commit 5810c4c94fe74665cd3824ce9526c703588e132d Author: Fabien Masson Date: Fri Aug 7 00:03:15 2026 +0200 renommage lassistanoque diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59be6f8 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +lassistanoque.db diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..a34799c --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,8 @@ +{ + "languages": { + "CSS": { + "format_on_save": "on", + "language_servers": ["tailwindcss-language-server"] + } + } +} diff --git a/artwork/logo-txt.png b/artwork/logo-txt.png new file mode 100644 index 0000000..9f17f68 Binary files /dev/null and b/artwork/logo-txt.png differ diff --git a/artwork/logo.png b/artwork/logo.png new file mode 100644 index 0000000..98679e2 Binary files /dev/null and b/artwork/logo.png differ diff --git a/artwork/logo.svg b/artwork/logo.svg new file mode 100644 index 0000000..31a2289 --- /dev/null +++ b/artwork/logo.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..eef5449 --- /dev/null +++ b/backend/.env @@ -0,0 +1 @@ +LASSISTANOQUE_JWT_SECRET=bCrRlJVsCmMhvKGbZFUbUPeYngvIDqJ6 diff --git a/backend/Makefile b/backend/Makefile new file mode 100644 index 0000000..8bea634 --- /dev/null +++ b/backend/Makefile @@ -0,0 +1,25 @@ +MODULE := trankilou.fr/agence +BINARY := agence +CMD := . + +VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "v0.0.0") +COMMIT := $(shell git rev-parse --short HEAD) +BUILD_DATE := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ') + +PKG := trankilou.fr/agence/commands + +LDFLAGS := -X '$(PKG).Version=$(VERSION)' \ + -X '$(PKG).CommitSHA=$(COMMIT)' \ + -X '$(PKG).BuildDate=$(BUILD_DATE)' + +.PHONY: run run_% + + +build: + (cd web; npm run build); + GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ../build/lassistanoque_linux_amd64 + GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ../build/lassistanoque_linux_arm64 + GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ../build/lassistanoque_windows_amd64 + GOOS=windows GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ../build/lassistanoque_windows_arm64 + GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ../build/lassistanoque_darwin_amd64 + GOOS=darwin GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ../build/lassistanoque_darwin_arm64 diff --git a/backend/cmd/migrate.go b/backend/cmd/migrate.go new file mode 100644 index 0000000..a3d6b45 --- /dev/null +++ b/backend/cmd/migrate.go @@ -0,0 +1,33 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "trankilou.fr/lassistanoque/backend/internal/adapter/database" +) + +func init() { + rootCmd.AddCommand( + newLigrateCmd(), + ) +} + +// newVersionCmd creates the version command for displaying version information. +func newLigrateCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "migrate", + Short: "migrate database", + Run: func(cmd *cobra.Command, args []string) { + db, err := database.GetDatabase() + if err != nil { + panic(err) + } + defer db.Close() + + err = db.Migrate() + if err != nil { + panic(err) + } + }, + } + return cmd +} diff --git a/backend/cmd/root.go b/backend/cmd/root.go new file mode 100644 index 0000000..3e41adc --- /dev/null +++ b/backend/cmd/root.go @@ -0,0 +1,24 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +// var ( +// cfgFile string +// ) + +// rootCmd is the root command for the agence CLI. +var rootCmd = &cobra.Command{ + Use: "lassistanoque", + Short: "lassistanoque: autonome agent", +} + +// Execute is the main entry point for the CLI application. +func Execute() { + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/backend/cmd/serve.go b/backend/cmd/serve.go new file mode 100644 index 0000000..6649ba0 --- /dev/null +++ b/backend/cmd/serve.go @@ -0,0 +1,78 @@ +package cmd + +import ( + "log/slog" + "time" + + "github.com/spf13/cobra" + "trankilou.fr/lassistanoque/backend/internal/adapter/auth/password" + "trankilou.fr/lassistanoque/backend/internal/adapter/database" + "trankilou.fr/lassistanoque/backend/internal/adapter/security" + "trankilou.fr/lassistanoque/backend/internal/http" + "trankilou.fr/lassistanoque/backend/internal/service/auth" + "trankilou.fr/lassistanoque/backend/internal/service/user" +) + +func init() { + rootCmd.AddCommand( + newServeCmd(), + ) +} + +func newServeCmd() *cobra.Command { + var short bool + cmd := &cobra.Command{ + Use: "serve", + Short: "Run server", + Run: func(cmd *cobra.Command, args []string) { + runServe() + }, + } + cmd.Flags().BoolVarP(&short, "short", "s", false, "show only version number") + return cmd +} + +func runServe() { + // database + db, err := database.GetDatabase() + if err != nil { + panic(err) + } + defer db.Close() + + err = db.Migrate() + if err != nil { + panic(err) + } + + settings, err := db.SettingsReporitory().GetSettings() + if err != nil { + panic(err) + } + + // Adapters + tokenManager := security.NewJwtTokenManager(12*time.Hour, 7*24*time.Hour, "lassistanoque") + pwdAuth := password.NewPasswordAuthenticator(tokenManager, db.UserReporitory()) + + // services + authService := auth.NewService( + db.SettingsReporitory(), + db.UserReporitory(), + map[string]auth.Authenticator{ + "password": pwdAuth, + }, + ) + userService := user.NewService(db.UserReporitory()) + + // http server + router := http.NewRouter(http.Dependencies{ + Settings: settings, + AuthService: authService, + UserService: userService, + TokenManager: tokenManager, + }) + + if err := router.Start(); err != nil { + slog.Error("failed to start server", "error", err) + } +} diff --git a/backend/cmd/version.go b/backend/cmd/version.go new file mode 100644 index 0000000..c4f8f0b --- /dev/null +++ b/backend/cmd/version.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand( + newVersionCmd(), + ) +} + +// Version, CommitSHA, and BuildDate are set at build time using ldflags. +var ( + Version = "dev" + CommitSHA = "none" + BuildDate = "unknown" +) + +// newVersionCmd creates the version command for displaying version information. +func newVersionCmd() *cobra.Command { + var short bool + cmd := &cobra.Command{ + Use: "version", + Short: "Show version", + Long: "Display version information for the agence application.", + Run: func(cmd *cobra.Command, args []string) { + if short { + fmt.Println(Version) + return + } + fmt.Printf("lassistanoque %s (commit: %s, built: %s)\n", Version, CommitSHA, BuildDate) + }, + } + cmd.Flags().BoolVarP(&short, "short", "s", false, "show only version number") + return cmd +} diff --git a/backend/internal/adapter/auth/password/argon2_hasher.go b/backend/internal/adapter/auth/password/argon2_hasher.go new file mode 100644 index 0000000..4e33f51 --- /dev/null +++ b/backend/internal/adapter/auth/password/argon2_hasher.go @@ -0,0 +1,130 @@ +package password + +import ( + "crypto/rand" + "crypto/subtle" + "encoding/base64" + "errors" + "fmt" + "strings" + + "golang.org/x/crypto/argon2" +) + +type params struct { + memory uint32 // en KiB + iterations uint32 + parallelism uint8 + saltLength uint32 + keyLength uint32 +} + +var defaultParams = ¶ms{ + memory: 64 * 1024, // 64 MB + iterations: 3, + parallelism: 2, + saltLength: 16, + keyLength: 32, +} + +// generateSalt crée un salt aléatoire cryptographiquement sûr +func generateSalt(length uint32) ([]byte, error) { + salt := make([]byte, length) + if _, err := rand.Read(salt); err != nil { + return nil, err + } + return salt, nil +} + +// HashPassword génère un hash Argon2id encodé (salt + hash + paramètres inclus) +func HashPassword(password string) (string, error) { + salt, err := generateSalt(defaultParams.saltLength) + if err != nil { + return "", err + } + + hash := argon2.IDKey( + []byte(password), + salt, + defaultParams.iterations, + defaultParams.memory, + defaultParams.parallelism, + defaultParams.keyLength, + ) + + // Format standard: $argon2id$v=19$m=65536,t=3,p=2$salt$hash + b64Salt := base64.RawStdEncoding.EncodeToString(salt) + b64Hash := base64.RawStdEncoding.EncodeToString(hash) + + encoded := fmt.Sprintf( + "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", + argon2.Version, + defaultParams.memory, + defaultParams.iterations, + defaultParams.parallelism, + b64Salt, + b64Hash, + ) + + return encoded, nil +} + +// VerifyPassword compare un mot de passe en clair avec un hash stocké +func VerifyPassword(password, encodedHash string) (bool, error) { + p, salt, hash, err := decodeHash(encodedHash) + if err != nil { + return false, err + } + + otherHash := argon2.IDKey( + []byte(password), + salt, + p.iterations, + p.memory, + p.parallelism, + p.keyLength, + ) + + // Comparaison en temps constant pour éviter les timing attacks + if subtle.ConstantTimeCompare(hash, otherHash) == 1 { + return true, nil + } + return false, nil +} + +// decodeHash parse la chaîne encodée pour en extraire params, salt et hash +func decodeHash(encodedHash string) (*params, []byte, []byte, error) { + parts := strings.Split(encodedHash, "$") + if len(parts) != 6 { + return nil, nil, nil, errors.New("format de hash invalide") + } + + var version int + _, err := fmt.Sscanf(parts[2], "v=%d", &version) + if err != nil { + return nil, nil, nil, err + } + if version != argon2.Version { + return nil, nil, nil, errors.New("version argon2 incompatible") + } + + p := ¶ms{} + _, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &p.memory, &p.iterations, &p.parallelism) + if err != nil { + return nil, nil, nil, err + } + + salt, err := base64.RawStdEncoding.DecodeString(parts[4]) + if err != nil { + return nil, nil, nil, err + } + p.saltLength = uint32(len(salt)) + + hash, err := base64.RawStdEncoding.DecodeString(parts[5]) + if err != nil { + return nil, nil, nil, err + } + p.keyLength = uint32(len(hash)) + + return p, salt, hash, nil +} diff --git a/backend/internal/adapter/auth/password/password.go b/backend/internal/adapter/auth/password/password.go new file mode 100644 index 0000000..9347463 --- /dev/null +++ b/backend/internal/adapter/auth/password/password.go @@ -0,0 +1,75 @@ +package password + +import ( + "context" + "fmt" + + "trankilou.fr/lassistanoque/backend/internal/domain" + "trankilou.fr/lassistanoque/backend/internal/service/auth" +) + +type PasswordAuthenticator struct { + tokenManager auth.TokenManager + userRepository domain.UserRepository +} + +func NewPasswordAuthenticator( + tokenManager auth.TokenManager, + userRepository domain.UserRepository, +) *PasswordAuthenticator { + return &PasswordAuthenticator{ + tokenManager, + userRepository, + } +} + +func (s *PasswordAuthenticator) Authenticate(ctx context.Context, creds auth.Credentials) (*auth.Session, error) { + + user, err := s.userRepository.FindUserByEmail(creds.Email) + if err != nil { + return nil, fmt.Errorf("Unauthorized user %s : %s", creds.Email, err) + } + + ok, err := VerifyPassword(creds.Password, user.Password) + if !ok { + return nil, fmt.Errorf("Password don't match") + } + if err != nil { + return nil, fmt.Errorf("Error verifying password: %s", err) + } + + accessToken, expiresAt, err := s.tokenManager.GenerateAccessToken(user) + if err != nil { + return nil, fmt.Errorf("Unable to generate access token: %s", err) + } + refreshToken, _, err := s.tokenManager.GenerateRefreshToken(user.ID) + if err != nil { + return nil, fmt.Errorf("Unable to generate refresh token: %s", err) + } + + return &auth.Session{ + User: user, + AccessToken: accessToken, + RefreshToken: refreshToken, + ExpiresAt: expiresAt, + }, nil +} + +func (s *PasswordAuthenticator) Register(ctx context.Context, registration auth.Registration) (*auth.Session, error) { + + hashedPassord, err := HashPassword(registration.Password) + if err != nil { + return nil, err + } + user, err := s.userRepository.CreateUser(&domain.User{ + Email: registration.Email, + Firstname: registration.Firstname, + Lastname: registration.Lastname, + Password: hashedPassord, + }) + if err != nil { + return nil, err + } + _ = user + return nil, nil +} diff --git a/backend/internal/adapter/database/dberrors/errors.go b/backend/internal/adapter/database/dberrors/errors.go new file mode 100644 index 0000000..ec37595 --- /dev/null +++ b/backend/internal/adapter/database/dberrors/errors.go @@ -0,0 +1,7 @@ +package dberrors + +import "errors" + +var ( + ErrNoRowUpdated = errors.New("No row was updated") +) diff --git a/backend/internal/adapter/database/factory.go b/backend/internal/adapter/database/factory.go new file mode 100644 index 0000000..9f805fb --- /dev/null +++ b/backend/internal/adapter/database/factory.go @@ -0,0 +1,26 @@ +package database + +import ( + "fmt" + + "trankilou.fr/lassistanoque/backend/internal/adapter/database/turso" + "trankilou.fr/lassistanoque/backend/internal/config" + "trankilou.fr/lassistanoque/backend/internal/domain" +) + +type Database interface { + SettingsReporitory() domain.SettingsRepository + UserReporitory() domain.UserRepository + Migrate() error + Close() +} + +func GetDatabase() (Database, error) { + cfg := config.GetConfig() + switch cfg.DatabaseType { + case "turso": + return turso.NewTursoDB(cfg) + default: + } + return nil, fmt.Errorf("Database %s not implemented", cfg.DatabaseType) +} diff --git a/backend/internal/adapter/database/turso/db.go b/backend/internal/adapter/database/turso/db.go new file mode 100644 index 0000000..c2fd818 --- /dev/null +++ b/backend/internal/adapter/database/turso/db.go @@ -0,0 +1,69 @@ +package turso + +import ( + "embed" + "fmt" + + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/sqlite3" + "github.com/golang-migrate/migrate/v4/source/iofs" + "github.com/jmoiron/sqlx" + _ "github.com/mattn/go-sqlite3" + "trankilou.fr/lassistanoque/backend/internal/config" + "trankilou.fr/lassistanoque/backend/internal/domain" + _ "turso.tech/database/tursogo" +) + +//go:embed migrations/* +var FS embed.FS + +type TursoDB struct { + DB *sqlx.DB +} + +func NewTursoDB(cfg *config.Config) (*TursoDB, error) { + db, err := sqlx.Connect("turso", cfg.DatabaseURL) + if err != nil { + return nil, fmt.Errorf("error connecting to db %: %s", cfg.DatabaseURL, err) + } + return &TursoDB{ + DB: db, + }, nil +} + +func (db *TursoDB) UserReporitory() domain.UserRepository { + return &TursoUserRepository{ + DB: db.DB, + } +} + +func (db *TursoDB) SettingsReporitory() domain.SettingsRepository { + return &TursoSettingsRepository{ + DB: db.DB, + } +} + +func (db *TursoDB) Close() { + db.DB.Close() +} + +func (db *TursoDB) Migrate() error { + sqldb := db.DB.DB + dbDriver, err := sqlite3.WithInstance(sqldb, &sqlite3.Config{}) + if err != nil { + return err + } + + sourceInstance, err := iofs.New(FS, "migrations") + if err != nil { + return err + } + + m, err := migrate.NewWithInstance("iofs", sourceInstance, "sqlite3", dbDriver) + if err != nil { + return err + } + + m.Up() + return nil +} diff --git a/backend/internal/adapter/database/turso/migrations/000001_create_tables.down.sql b/backend/internal/adapter/database/turso/migrations/000001_create_tables.down.sql new file mode 100644 index 0000000..e0659d8 --- /dev/null +++ b/backend/internal/adapter/database/turso/migrations/000001_create_tables.down.sql @@ -0,0 +1,10 @@ +drop table providers; +drop table models; +drop table system; +drop table oidc; +drop table users; +drop table user_addresses; +drop table channels; +drop table tools; +drop table tasks; +drop table history; diff --git a/backend/internal/adapter/database/turso/migrations/000001_create_tables.up.sql b/backend/internal/adapter/database/turso/migrations/000001_create_tables.up.sql new file mode 100644 index 0000000..35663d8 --- /dev/null +++ b/backend/internal/adapter/database/turso/migrations/000001_create_tables.up.sql @@ -0,0 +1,95 @@ +create table providers ( + id text not null primary key, + name text, + key text, + url text, + _version text +); + +create table models ( + id text not null primary key, + provider_id text, + name text, + modelname text, + configuration text, + _version text +); + +create table settings ( + chat_model_id text, + default_lang text, + register_enabled numeric, + password_enabled numeric, + _version text +); + +create table oidc ( + id string not null primary key, + label string, + domain string, + client_id string, + client_secret string, + wellknown_url string, + _version text +); + +create table users ( + id string not null primary key, + firstname string not null default '', + lastname string not null default '', + password string not null default '', + email string not null unique, + picture string not null default '', + enabled numeric default true, + administrator numeric default false, + _version text not null +); + +create table user_addresses ( + id string not null primary key, + user_id string, + type string, + address string, + _version text +); + +create table channels ( + id string not null primary key, + name string, + type string, + enabled numeric, + configuration string, + _version text +); + +create table tools ( + id string not null primary key, + name string, + type string, + enabled numeric, + configuration string, + _version text +); + +create table tasks ( + id string not null primary key, + owner_id string, + model_id string, + label string, + prompt string, + cron string, + status string, + next_datetime numeric, + _version text +); + +create table history ( + id string not null primary key, + task_id string, + start_datetimle numeric, + end_datetime numeric, + prompt string, + log string, + response string, + _version text +); diff --git a/backend/internal/adapter/database/turso/migrations/000002_insert_system.down.sql b/backend/internal/adapter/database/turso/migrations/000002_insert_system.down.sql new file mode 100644 index 0000000..e15e9d4 --- /dev/null +++ b/backend/internal/adapter/database/turso/migrations/000002_insert_system.down.sql @@ -0,0 +1 @@ +delete from sytem; diff --git a/backend/internal/adapter/database/turso/migrations/000002_insert_system.up.sql b/backend/internal/adapter/database/turso/migrations/000002_insert_system.up.sql new file mode 100644 index 0000000..79fc8c7 --- /dev/null +++ b/backend/internal/adapter/database/turso/migrations/000002_insert_system.up.sql @@ -0,0 +1,2 @@ +insert into settings (default_lang, register_enabled, password_enabled, _version) +values ('en', true, true, 'init'); diff --git a/backend/internal/adapter/database/turso/settings_repository.go b/backend/internal/adapter/database/turso/settings_repository.go new file mode 100644 index 0000000..41aadcb --- /dev/null +++ b/backend/internal/adapter/database/turso/settings_repository.go @@ -0,0 +1,45 @@ +package turso + +import ( + "github.com/jmoiron/sqlx" + "trankilou.fr/lassistanoque/backend/internal/adapter/database/dberrors" + "trankilou.fr/lassistanoque/backend/internal/domain" + "trankilou.fr/lassistanoque/backend/internal/utility" +) + +type TursoSettingsRepository struct { + DB *sqlx.DB +} + +func (sr *TursoSettingsRepository) GetSettings() (*domain.Settings, error) { + var settings domain.Settings + err := sr.DB.Get(&settings, "select * from settings limit 1") + return &settings, err +} + +func (sr *TursoSettingsRepository) UpdateSettings(setting *domain.Settings) (*domain.Settings, error) { + newVersion := utility.GenID() + + res, err := sr.DB.Exec( + `update settings + set chat_model_id=$1, + default_lang=$2, + register_enabled=$3, + password_enabled=$4, + _version=:$5 + where _version=$6`, + setting.ChatModelID, setting.DefaultLang, setting.RegisterEnabled, + setting.PasswordEnabled, newVersion, setting.VersionId, + ) + if err != nil { + return nil, err + } + + if count, _ := res.RowsAffected(); count == 0 { + return nil, dberrors.ErrNoRowUpdated + } + + setting.VersionId = newVersion + + return setting, nil +} diff --git a/backend/internal/adapter/database/turso/user_repository.go b/backend/internal/adapter/database/turso/user_repository.go new file mode 100644 index 0000000..9a2eee1 --- /dev/null +++ b/backend/internal/adapter/database/turso/user_repository.go @@ -0,0 +1,93 @@ +package turso + +import ( + "github.com/jmoiron/sqlx" + "trankilou.fr/lassistanoque/backend/internal/adapter/database/dberrors" + "trankilou.fr/lassistanoque/backend/internal/domain" + "trankilou.fr/lassistanoque/backend/internal/utility" +) + +type TursoUserRepository struct { + DB *sqlx.DB +} + +func (ur *TursoUserRepository) FindUser(id string) (*domain.User, error) { + var user domain.User + err := ur.DB.Get(&user, "select * from users where id=$1", id) + return &user, err +} + +func (ur *TursoUserRepository) FindUserByEmail(email string) (*domain.User, error) { + var user domain.User + err := ur.DB.Get(&user, "select * from users where email=$1", email) + return &user, err +} + +func (ur *TursoUserRepository) CountUsers() (int, error) { + var count int + err := ur.DB.Get(&count, "select count(*) from users") + return count, err +} + +func (ur *TursoUserRepository) ListUsers() ([]*domain.User, error) { + var users []*domain.User + err := ur.DB.Select(&users, "select * from users order by lastname, firstname") + return users, err +} + +func (ur *TursoUserRepository) CreateUser(user *domain.User) (*domain.User, error) { + + count, err := ur.CountUsers() + if err != nil { + return nil, err + } + if count == 0 { + user.Administrator = true + } + user.ID = utility.GenID() + user.VersionId = utility.GenID() + + ur.DB.NamedExec( + `insert into users (id, email, firstname, lastname, enabled, password, administrator, _version) + values (:id, :email, :firstname, :lastname, :enabled, :password, :administrator, :_version)`, + user) + return nil, nil +} + +func (ur *TursoUserRepository) UpdateUser(user *domain.User) (*domain.User, error) { + + newVersion := utility.GenID() + + res, err := ur.DB.Exec( + `update users + set email=$1, + firstname=$2, + lastname=$3, + enabled=$4, + administrator=$5 + _version=:$6 + where id=$7 and _version=$8`, + user.Email, user.Firstname, user.Lastname, + user.Enabled, user.Administrator, + newVersion, user.ID, user.VersionId, + ) + if err != nil { + return nil, err + } + + if count, _ := res.RowsAffected(); count == 0 { + return nil, dberrors.ErrNoRowUpdated + } + + user.VersionId = newVersion + + return user, nil +} + +func (ur *TursoUserRepository) DeleteUser(id string) error { + res, err := ur.DB.Exec("delete from users where id=$1", id) + if count, _ := res.RowsAffected(); count == 0 { + return dberrors.ErrNoRowUpdated + } + return err +} diff --git a/backend/internal/adapter/security/jwt.go b/backend/internal/adapter/security/jwt.go new file mode 100644 index 0000000..dca8e78 --- /dev/null +++ b/backend/internal/adapter/security/jwt.go @@ -0,0 +1,129 @@ +package security + +import ( + "errors" + "net/http" + "strings" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/labstack/echo/v5" + "trankilou.fr/lassistanoque/backend/internal/config" + "trankilou.fr/lassistanoque/backend/internal/domain" + "trankilou.fr/lassistanoque/backend/internal/service/auth" +) + +// Claims personnalisées embarquées dans le JWT. +type Claims struct { + UserID string `json:"uid"` + Email string `json:"email"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Administrator bool `json:"administrator"` + jwt.RegisteredClaims +} + +type JwtTokenManager struct { + secret []byte + accessTTL time.Duration + refreshTTL time.Duration + issuer string +} + +func NewJwtTokenManager(accessTTL, refreshTTL time.Duration, issuer string) *JwtTokenManager { + cfg := config.GetConfig() + return &JwtTokenManager{ + secret: []byte(cfg.JWTSecret), + accessTTL: accessTTL, + refreshTTL: refreshTTL, + issuer: issuer, + } +} + +// GenerateAccessToken crée un JWT de courte durée (ex: 15 min) utilisé +// pour authentifier les requêtes API. +func (tm *JwtTokenManager) GenerateAccessToken(user *domain.User) (string, time.Time, error) { + expiresAt := time.Now().Add(tm.accessTTL) + claims := Claims{ + UserID: user.ID, + Email: user.Email, + Firstname: user.Firstname, + Lastname: user.Lastname, + Administrator: user.Administrator, + RegisteredClaims: jwt.RegisteredClaims{ + Issuer: tm.issuer, + Subject: user.ID, + ExpiresAt: jwt.NewNumericDate(expiresAt), + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + signed, err := token.SignedString(tm.secret) + return signed, expiresAt, err +} + +// GenerateRefreshToken crée un token de longue durée (ex: 7 jours) utilisé +// uniquement pour obtenir un nouvel access token, jamais pour appeler l'API directement. +func (tm *JwtTokenManager) GenerateRefreshToken(userID string) (string, time.Time, error) { + expiresAt := time.Now().Add(tm.refreshTTL) + claims := jwt.RegisteredClaims{ + Issuer: tm.issuer, + Subject: userID, + ExpiresAt: jwt.NewNumericDate(expiresAt), + IssuedAt: jwt.NewNumericDate(time.Now()), + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + signed, err := token.SignedString(tm.secret) + return signed, expiresAt, err +} + +// ParseAndValidate décode et vérifie la signature + l'expiration d'un JWT. +func (tm *JwtTokenManager) ParseAndValidate(tokenString string) (*domain.User, error) { + claims := &Claims{} + token, err := jwt.ParseWithClaims(tokenString, claims, func(t *jwt.Token) (interface{}, error) { + if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, errors.New("méthode de signature inattendue") + } + return tm.secret, nil + }) + if err != nil { + return nil, err + } + if !token.Valid { + return nil, errors.New("token invalide") + } + return &domain.User{ + ID: claims.UserID, + Email: claims.Email, + Firstname: claims.Firstname, + Lastname: claims.Lastname, + Administrator: claims.Administrator, + }, nil +} + +func (tm *JwtTokenManager) TokenMiddleware(next echo.HandlerFunc) echo.HandlerFunc { + return func(c *echo.Context) error { + header := c.Request().Header.Get("Authorization") + if header == "" { + return echo.NewHTTPError(http.StatusUnauthorized, "en-tête Authorization manquant") + } + + parts := strings.SplitN(header, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return echo.NewHTTPError(http.StatusUnauthorized, "format attendu: Bearer ") + } + + claims, err := tm.ParseAndValidate(parts[1]) + if err != nil { + return echo.NewHTTPError(http.StatusUnauthorized, "token invalide ou expiré") + } + + // Rend les infos disponibles aux handlers suivants via c.Get(...) + c.Set(auth.ContextUserIDKey, claims.ID) + c.Set(auth.ContextEmailKey, claims.Email) + c.Set(auth.ContextNameKey, claims.Firstname+" "+claims.Lastname) + c.Set(auth.ContextAdminKey, claims.Administrator) + + return next(c) + } +} diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go new file mode 100644 index 0000000..eb8515e --- /dev/null +++ b/backend/internal/config/config.go @@ -0,0 +1,56 @@ +package config + +import ( + "log" + "os" + "strconv" + + "github.com/joho/godotenv" +) + +const ( + DEFAULT_DB_TYPE = "turso" + DEFAULT_DB_URL = "lassistanoque.db" + DEFAULT_HTTP_PORT = 3000 +) + +type Config struct { + DatabaseType string + DatabaseURL string + HttpPort int + JWTSecret string +} + +var config *Config + +func GetConfig() *Config { + + if config == nil { + godotenv.Load() + + databaseType := os.Getenv("LASSISTANOQUE_DB_TYPE") + if databaseType == "" { + databaseType = DEFAULT_DB_TYPE + } + databaseURL := os.Getenv("LASSISTANOQUE_DB_URL") + if databaseURL == "" { + databaseURL = DEFAULT_DB_URL + } + httpPort, _ := strconv.Atoi(os.Getenv("LASSISTANOQUE_HTTP_PORT")) + if httpPort == 0 { + httpPort = DEFAULT_HTTP_PORT + } + jwtsecret := os.Getenv("LASSISTANOQUE_JWT_SECRET") + if jwtsecret == "" { + log.Fatal("LASSISTANOQUE_JWT_SECRET must be set") + } + + config = &Config{ + DatabaseType: databaseType, + DatabaseURL: databaseURL, + HttpPort: httpPort, + } + + } + return config +} diff --git a/backend/internal/domain/settings.go b/backend/internal/domain/settings.go new file mode 100644 index 0000000..253a99e --- /dev/null +++ b/backend/internal/domain/settings.go @@ -0,0 +1,16 @@ +package domain + +import "database/sql" + +type Settings struct { + ChatModelID sql.NullString `db:"chat_model_id"` + DefaultLang string `db:"default_lang"` + RegisterEnabled bool `db:"register_enabled"` + PasswordEnabled bool `db:"password_enabled"` + VersionId string `db:"_version"` +} + +type SettingsRepository interface { + GetSettings() (*Settings, error) + UpdateSettings(config *Settings) (*Settings, error) +} diff --git a/backend/internal/domain/user.go b/backend/internal/domain/user.go new file mode 100644 index 0000000..549b265 --- /dev/null +++ b/backend/internal/domain/user.go @@ -0,0 +1,23 @@ +package domain + +type User struct { + ID string `db:"id" json:"id"` + Firstname string `db:"firstname" json:"firstname,omitempty"` + Lastname string `db:"lastname" json:"lastname,omitempty"` + Password string `db:"password" json:"-"` + Email string `db:"email" json:"email,omitempty"` + PictureID string `db:"picture" json:"picture,omitempty"` + Enabled bool `db:"enabled" json:"enabled"` + Administrator bool `db:"administrator" json:"administrator"` + VersionId string `db:"_version" json:"-"` +} + +type UserRepository interface { + CountUsers() (int, error) + FindUser(id string) (*User, error) + FindUserByEmail(email string) (*User, error) + ListUsers() ([]*User, error) + CreateUser(user *User) (*User, error) + UpdateUser(user *User) (*User, error) + DeleteUser(id string) error +} diff --git a/backend/internal/http/handlers/auth.go b/backend/internal/http/handlers/auth.go new file mode 100644 index 0000000..5c0ee7d --- /dev/null +++ b/backend/internal/http/handlers/auth.go @@ -0,0 +1,91 @@ +package handlers + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/labstack/echo/v5" + "trankilou.fr/lassistanoque/backend/internal/domain" + "trankilou.fr/lassistanoque/backend/internal/service/auth" +) + +func NewAuthGroup(prefix string, e *echo.Group, service *auth.Service) *echo.Group { + authHandler := &AuthHandler{ + authService: service, + } + auth := e.Group(prefix) + auth.POST("/login", authHandler.Login) + auth.GET("/oidc/callback", authHandler.OIDCCallback) + auth.POST("/register", authHandler.Register) + return auth +} + +type AuthHandler struct { + authService *auth.Service +} + +type loginRequest struct { + Email string `json:"email" validate:"required,email"` + Password string `json:"password" validate:"required"` +} + +type loginResponse struct { + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` + ExpiresAt string `json:"expiresAt"` + User *domain.User `json:"user"` +} + +func (h AuthHandler) Login(c *echo.Context) error { + var req loginRequest + if err := c.Bind(&req); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "invalid request") + } + if req.Email == "" || req.Password == "" { + return echo.NewHTTPError(http.StatusBadRequest, "email and password are required") + } + + creds := auth.Credentials{ + Method: "password", + Email: req.Email, + Password: req.Password, + } + + session, err := h.authService.Login(context.Background(), creds) + if err != nil { + c.Logger().Error(fmt.Sprintf("error while login: %s", err)) + return echo.NewHTTPError(http.StatusUnauthorized, "login error") + } + + resp := loginResponse{ + AccessToken: session.AccessToken, + RefreshToken: session.RefreshToken, + ExpiresAt: session.ExpiresAt.Format("2006-01-02T15:04:05Z07:00"), + } + resp.User = session.User + + return c.JSON(http.StatusOK, resp) +} + +func (h AuthHandler) OIDCCallback(c *echo.Context) error { + return nil +} + +func (h AuthHandler) Register(c *echo.Context) error { + + reader := c.Request().Body + var registration auth.Registration + decoder := json.NewDecoder(reader) + err := decoder.Decode(®istration) + if err != nil { + return c.String(http.StatusBadRequest, "malformatted registration") + } + + user, err := h.authService.Register(registration) + if err != nil { + return c.String(http.StatusBadRequest, err.Error()) + } + return c.JSON(http.StatusCreated, user) +} diff --git a/backend/internal/http/handlers/user.go b/backend/internal/http/handlers/user.go new file mode 100644 index 0000000..ef507e4 --- /dev/null +++ b/backend/internal/http/handlers/user.go @@ -0,0 +1,33 @@ +package handlers + +import ( + "net/http" + + "github.com/labstack/echo/v5" + "trankilou.fr/lassistanoque/backend/internal/service/auth" + "trankilou.fr/lassistanoque/backend/internal/service/user" +) + +func NewUserGroup(prefix string, e *echo.Group, service *user.Service, middlewares ...echo.MiddlewareFunc) *echo.Group { + userHandler := &UserHandler{ + userService: service, + } + _ = userHandler + auth := e.Group(prefix, middlewares...) + auth.GET("/me", userHandler.Me) + // auth.GET("/register", userHandler.Register) + return auth +} + +type UserHandler struct { + userService *user.Service +} + +func (h UserHandler) Me(c *echo.Context) error { + userID := c.Get(auth.ContextUserIDKey).(string) + user, err := h.userService.GetUser(userID) + if err != nil { + return echo.NewHTTPError(http.StatusForbidden, err.Error()) + } + return c.JSON(http.StatusOK, user) +} diff --git a/backend/internal/http/router.go b/backend/internal/http/router.go new file mode 100644 index 0000000..4e77d3d --- /dev/null +++ b/backend/internal/http/router.go @@ -0,0 +1,85 @@ +package http + +import ( + "embed" + "fmt" + "io/fs" + "net/http" + + "github.com/labstack/echo/v5" + "github.com/labstack/echo/v5/middleware" + "trankilou.fr/lassistanoque/backend/internal/config" + "trankilou.fr/lassistanoque/backend/internal/domain" + "trankilou.fr/lassistanoque/backend/internal/http/handlers" + "trankilou.fr/lassistanoque/backend/internal/service/auth" + "trankilou.fr/lassistanoque/backend/internal/service/user" +) + +//go:embed web/_app/* +var appFiles embed.FS + +//go:embed web/assets/* +var assetsFiles embed.FS + +//go:embed web/index.html +var indexhtml []byte + +//go:embed web/robots.txt +var robotstxt []byte + +type Router struct { + settings *domain.Settings + echo *echo.Echo +} + +type Dependencies struct { + Settings *domain.Settings + AuthService *auth.Service + UserService *user.Service + TokenManager auth.TokenManager +} + +func NewRouter(deps Dependencies) *Router { + + e := echo.New() + e.Use(middleware.RequestLogger()) + e.Use(middleware.CORS("http://localhost:5173")) + + // SPA + fsapp, err := fs.Sub(appFiles, "web/_app") + if err != nil { + panic(err) + } + fsassets, err := fs.Sub(assetsFiles, "web/assets") + if err != nil { + panic(err) + } + appHandler := http.FileServer(http.FS(fsapp)) + assetsHandler := http.FileServer(http.FS(fsassets)) + e.GET("/_app/*", echo.WrapHandler(http.StripPrefix("/_app/", appHandler))) + e.GET("/assets/*", echo.WrapHandler(http.StripPrefix("/assets/", assetsHandler))) + e.GET("/robots.txt", func(c *echo.Context) error { + return c.Blob(http.StatusOK, "plain/text", robotstxt) + }) + e.GET("/*", func(c *echo.Context) error { + fmt.Println("match /*") + fmt.Println(string(indexhtml)) + return c.Blob(http.StatusOK, "text/html", indexhtml) + }) + + // API + api := e.Group("/api") + _ = handlers.NewAuthGroup("/auth", api, deps.AuthService) + _ = handlers.NewUserGroup("/user", api, deps.UserService, deps.TokenManager.TokenMiddleware) + //_ = handlers.NewUserGroup("/user", e, deps.UserService) + + return &Router{ + settings: deps.Settings, + echo: e, + } +} + +func (r *Router) Start() error { + cfg := config.GetConfig() + return r.echo.Start(fmt.Sprintf(":%d", cfg.HttpPort)) +} diff --git a/backend/internal/http/web/_app/immutable/assets/0.CHStj0oN.css b/backend/internal/http/web/_app/immutable/assets/0.CHStj0oN.css new file mode 100644 index 0000000..e3f7e87 --- /dev/null +++ b/backend/internal/http/web/_app/immutable/assets/0.CHStj0oN.css @@ -0,0 +1,2 @@ +/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-outline-style:solid;--tw-font-weight:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-sky-500:oklch(68.5% .169 237.323);--color-stone-100:oklch(97% .001 106.424);--color-stone-200:oklch(92.3% .003 48.717);--color-stone-300:oklch(86.9% .005 56.366);--color-stone-400:oklch(70.9% .01 56.259);--color-stone-600:oklch(44.4% .011 73.639);--color-stone-700:oklch(37.4% .01 67.558);--color-stone-800:oklch(26.8% .007 34.298);--color-stone-900:oklch(21.6% .006 56.043);--color-white:#fff;--spacing:.25rem;--container-3xs:16rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-bold:700;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}input:where([type=text]),input:where(:not([type])),input:where([type=email]),input:where([type=url]),input:where([type=password]),input:where([type=number]),input:where([type=date]),input:where([type=datetime-local]),input:where([type=month]),input:where([type=search]),input:where([type=tel]),input:where([type=time]),input:where([type=week]),select:where([multiple]),textarea,select{appearance:none;--tw-shadow:0 0 #0000;background-color:#fff;border-width:1px;border-color:oklch(55.1% .027 264.364);border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem}:is(input:where([type=text]),input:where(:not([type])),input:where([type=email]),input:where([type=url]),input:where([type=password]),input:where([type=number]),input:where([type=date]),input:where([type=datetime-local]),input:where([type=month]),input:where([type=search]),input:where([type=tel]),input:where([type=time]),input:where([type=week]),select:where([multiple]),textarea,select):focus{outline-offset:2px;--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:oklch(54.6% .245 262.881);--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-color:oklch(54.6% .245 262.881);outline:2px solid #0000}input::placeholder,textarea::placeholder{color:oklch(55.1% .027 264.364);opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit{padding-top:0;padding-bottom:0}::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0}::-webkit-datetime-edit-month-field{padding-top:0;padding-bottom:0}::-webkit-datetime-edit-day-field{padding-top:0;padding-bottom:0}::-webkit-datetime-edit-hour-field{padding-top:0;padding-bottom:0}::-webkit-datetime-edit-minute-field{padding-top:0;padding-bottom:0}::-webkit-datetime-edit-second-field{padding-top:0;padding-bottom:0}::-webkit-datetime-edit-millisecond-field{padding-top:0;padding-bottom:0}::-webkit-datetime-edit-meridiem-field{padding-top:0;padding-bottom:0}select{-webkit-print-color-adjust:exact;print-color-adjust:exact;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='oklch(55.1%25 0.027 264.364)' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem}select:where([multiple]),select:where([size]:not([size="1"])){background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;print-color-adjust:unset;padding-right:.75rem}input:where([type=checkbox]),input:where([type=radio]){appearance:none;-webkit-print-color-adjust:exact;print-color-adjust:exact;vertical-align:middle;-webkit-user-select:none;user-select:none;color:oklch(54.6% .245 262.881);--tw-shadow:0 0 #0000;background-color:#fff;background-origin:border-box;border-width:1px;border-color:oklch(55.1% .027 264.364);flex-shrink:0;width:1rem;height:1rem;padding:0;display:inline-block}input:where([type=checkbox]){border-radius:0}input:where([type=radio]){border-radius:100%}input:where([type=checkbox]):focus,input:where([type=radio]):focus{outline-offset:2px;--tw-ring-inset:var(--tw-empty, );--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:oklch(54.6% .245 262.881);--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);outline:2px solid #0000}input:where([type=checkbox]):checked,input:where([type=radio]):checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}input:where([type=checkbox]):checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}@media (forced-colors:active){input:where([type=checkbox]):checked{appearance:auto}}input:where([type=radio]):checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}@media (forced-colors:active){input:where([type=radio]):checked{appearance:auto}}input:where([type=checkbox]):checked:hover,input:where([type=checkbox]):checked:focus,input:where([type=radio]):checked:hover,input:where([type=radio]):checked:focus{background-color:currentColor;border-color:#0000}input:where([type=checkbox]):indeterminate{background-color:currentColor;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}@media (forced-colors:active){input:where([type=checkbox]):indeterminate{appearance:auto}}input:where([type=checkbox]):indeterminate:hover,input:where([type=checkbox]):indeterminate:focus{background-color:currentColor;border-color:#0000}input:where([type=file]){background:unset;border-color:inherit;font-size:unset;line-height:inherit;border-width:0;border-radius:0;padding:0}input:where([type=file]):focus{outline:1px solid buttontext;outline:1px auto -webkit-focus-ring-color}}@layer components;@layer utilities{.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.m-54{margin:calc(var(--spacing) * 54)}.m-71{margin:calc(var(--spacing) * 71)}.m-143{margin:calc(var(--spacing) * 143)}.m-209{margin:calc(var(--spacing) * 209)}.m-214{margin:calc(var(--spacing) * 214)}.m-357{margin:calc(var(--spacing) * 357)}.m-572{margin:calc(var(--spacing) * 572)}.m-auto{margin:auto}.my-1{margin-block:var(--spacing)}.my-2{margin-block:calc(var(--spacing) * 2)}.my-4{margin-block:calc(var(--spacing) * 4)}.mr-1{margin-right:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.ml-4{margin-left:calc(var(--spacing) * 4)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.h-8{height:calc(var(--spacing) * 8)}.h-full{height:100%}.w-24{width:calc(var(--spacing) * 24)}.w-full{width:100%}.min-w-64{min-width:calc(var(--spacing) * 64)}.flex-1{flex:1}.grid-cols-\[repeat\(auto-fit\,minmax\(150px\,1fr\)\)\]{grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.items-center{align-items:center}.gap-4{gap:calc(var(--spacing) * 4)}.overflow-y-scroll{overflow-y:scroll}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-stone-200{border-color:var(--color-stone-200)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.text-center{text-align:center}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.italic{font-style:italic}.outline{outline-style:var(--tw-outline-style);outline-width:1px}@media (prefers-color-scheme:dark){.dark\:border-stone-800{border-color:var(--color-stone-800)}}}html{height:100%}body{background-color:var(--color-stone-100);height:100%;color:var(--color-stone-900)}@media (prefers-color-scheme:dark){body{background-color:var(--color-stone-900);color:var(--color-stone-200)}}a{color:var(--color-sky-500)}h1{margin-bottom:calc(var(--spacing) * 4);font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}label{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-stone-600);text-transform:uppercase}@media (prefers-color-scheme:dark){label{color:var(--color-stone-400)}}hr{border-color:var(--color-stone-200)}@media (prefers-color-scheme:dark){hr{border-color:var(--color-stone-800)}}.textinput{background-color:var(--color-white);width:100%;padding:var(--spacing);color:var(--color-stone-900);border-radius:.25rem}@media (prefers-color-scheme:dark){.textinput{background-color:var(--color-stone-700);color:var(--color-stone-100)}}.pointer{cursor:pointer}.bt{cursor:pointer;background-color:var(--color-stone-200);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);border-radius:.25rem}@media (hover:hover){.bt:hover{background-color:var(--color-stone-300)}}@media (prefers-color-scheme:dark){.bt{background-color:var(--color-stone-800)}@media (hover:hover){.bt:hover{background-color:var(--color-stone-700)}}}#outer{flex-direction:row;height:100%;display:flex}#main{flex-direction:column;width:100%;display:flex}#sidebar{width:var(--container-3xs);border-right-style:var(--tw-border-style);border-right-width:1px;border-right-color:var(--color-stone-200);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;transition-duration:.3s}@media (prefers-color-scheme:dark){#sidebar{border-color:var(--color-stone-800)}}#header{height:calc(var(--spacing) * 10);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-right-color:var(--color-stone-200);padding:calc(var(--spacing) * 2)}@media (prefers-color-scheme:dark){#header{border-color:var(--color-stone-800)}}#content{padding:calc(var(--spacing) * 2)}#hamburgerDesktop,#hamburgerMobile{display:none}@media (width>=640px){#hamburgerDesktop{display:inline}.desktopMenuClosed #sidebar{width:calc(var(--spacing) * 10)}}@media not all and (width>=640px){#hamburgerMobile{display:inline}#main{margin-left:calc(var(--spacing) * 10)}#sidebar{background-color:var(--color-stone-100);width:100%;height:100%;position:absolute;left:0}@media (prefers-color-scheme:dark){#sidebar{background-color:var(--color-stone-900)}}.mobileMenuClosed #sidebar{width:calc(var(--spacing) * 10);position:absolute}}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false} diff --git a/backend/internal/http/web/_app/immutable/chunks/BXe04aPf.js b/backend/internal/http/web/_app/immutable/chunks/BXe04aPf.js new file mode 100644 index 0000000..1770f89 --- /dev/null +++ b/backend/internal/http/web/_app/immutable/chunks/BXe04aPf.js @@ -0,0 +1,3 @@ +var e=Object.defineProperty,t=(t,n)=>{let r={};for(var i in t)e(r,i,{get:t[i],enumerable:!0});return n||e(r,Symbol.toStringTag,{value:`Module`}),r},n=Array.isArray,r=Array.prototype.indexOf,i=Array.prototype.includes,a=Array.from,o=Object.defineProperty,s=Object.getOwnPropertyDescriptor,c=Object.getOwnPropertyDescriptors,l=Object.prototype,u=Array.prototype,d=Object.getPrototypeOf,f=Object.isExtensible,p=()=>{};function m(e){for(var t=0;t{e=n,t=r}),resolve:e,reject:t}}var g=1024,_=2048,v=4096,y=8192,ee=16384,te=32768,ne=1<<25,re=65536,ie=1<<18,ae=1<<19,oe=1<<20,se=65536,ce=1<<21,le=1<<22,ue=1<<23,de=Symbol(`$state`),fe=Symbol(`legacy props`),pe=Symbol(``),me=Symbol(`attributes`),he=Symbol(`class`),ge=Symbol(`style`),_e=Symbol(`text`),ve=Symbol(`form reset`),ye=new class extends Error{name=`StaleReactionError`;message="The reaction that called `getAbortSignal()` was re-run or destroyed"},be=!!globalThis.document?.contentType&&globalThis.document.contentType.includes(`xml`);function xe(e){throw Error(`https://svelte.dev/e/experimental_async_required`)}function Se(e){throw Error(`https://svelte.dev/e/lifecycle_outside_component`)}function Ce(){throw Error(`https://svelte.dev/e/missing_context`)}function we(){throw Error(`https://svelte.dev/e/async_derived_orphan`)}function Te(e){throw Error(`https://svelte.dev/e/effect_in_teardown`)}function Ee(){throw Error(`https://svelte.dev/e/effect_in_unowned_derived`)}function De(e){throw Error(`https://svelte.dev/e/effect_orphan`)}function Oe(){throw Error(`https://svelte.dev/e/effect_update_depth_exceeded`)}function ke(){throw Error(`https://svelte.dev/e/fork_discarded`)}function Ae(){throw Error(`https://svelte.dev/e/fork_timing`)}function je(){throw Error(`https://svelte.dev/e/get_abort_signal_outside_reaction`)}function Me(){throw Error(`https://svelte.dev/e/hydration_failed`)}function Ne(e){throw Error(`https://svelte.dev/e/lifecycle_legacy_only`)}function Pe(e){throw Error(`https://svelte.dev/e/props_invalid_value`)}function Fe(){throw Error(`https://svelte.dev/e/state_descriptors_fixed`)}function Ie(){throw Error(`https://svelte.dev/e/state_prototype_fixed`)}function Le(){throw Error(`https://svelte.dev/e/state_unsafe_mutation`)}function Re(){throw Error(`https://svelte.dev/e/svelte_boundary_reset_onerror`)}var ze={},b=Symbol(`uninitialized`),Be=`http://www.w3.org/1999/xhtml`;function Ve(){console.warn(`https://svelte.dev/e/derived_inert`)}function He(e){console.warn(`https://svelte.dev/e/hydratable_missing_but_expected`)}function Ue(e){console.warn(`https://svelte.dev/e/hydration_mismatch`)}function We(){console.warn(`https://svelte.dev/e/svelte_boundary_reset_noop`)}var x=!1;function S(e){x=e}var C;function w(e){if(e===null)throw Ue(),ze;return C=e}function Ge(){return w(L(C))}function Ke(e){if(x){if(L(C)!==null)throw Ue(),ze;C=e}}function qe(e=1){if(x){for(var t=e,n=C;t--;)n=L(n);C=n}}function Je(e=!0){for(var t=0,n=C;;){if(n.nodeType===8){var r=n.data;if(r===`]`){if(t===0)return n;--t}else(r===`[`||r===`[!`||r[0]===`[`&&!isNaN(Number(r.slice(1))))&&(t+=1)}var i=L(n);e&&n.remove(),n=i}}function Ye(e){if(!e||e.nodeType!==8)throw Ue(),ze;return e.data}function Xe(e){return e===this.v}function Ze(e,t){return e==e?e!==t||typeof e==`object`&&!!e||typeof e==`function`:t==t}function Qe(e){return!Ze(e,this.v)}var T=null;function $e(e){T=e}function et(){let e={};return[()=>(rt(e)||Ce(),tt(e)),t=>nt(e,t)]}function tt(e){return ct(`getContext`).get(e)}function nt(e,t){return ct(`setContext`).set(e,t),t}function rt(e){return ct(`hasContext`).has(e)}function it(){return ct(`getAllContexts`)}function at(e,t=!1,n){T={p:T,i:!1,c:null,e:null,s:e,x:null,r:G,l:null}}function ot(e){var t=T,n=t.e;if(n!==null){t.e=null;for(var r of n)Ln(r)}return e!==void 0&&(t.x=e),t.i=!0,T=t.p,e??{}}function st(){return!0}function ct(e){return T===null&&Se(e),T.c??=new Map(lt(T)||void 0)}function lt(e){let t=e.p;for(;t!==null;){let e=t.c;if(e!==null)return e;t=t.p}return null}var ut=[];function dt(){var e=ut;ut=[],m(e)}function E(e){if(ut.length===0&&!Jt){var t=ut;queueMicrotask(()=>{t===ut&&dt()})}ut.push(e)}function ft(){for(;ut.length>0;)dt()}function pt(e){var t=G;if(t===null)return H.f|=ue,e;if(!(t.f&32768)&&!(t.f&4))throw e;D(e,t)}function D(e,t){if(!(t!==null&&t.f&16384)){for(;t!==null;){if(t.f&128){if(!(t.f&32768))throw e;try{t.b.error(e);return}catch(t){e=t}}t=t.parent}throw e}}var mt=~(_|v|g);function O(e,t){e.f=e.f&mt|t}function ht(e){e.f&512||e.deps===null?O(e,g):O(e,v)}function gt(e){if(e!==null)for(let t of e)!(t.f&2)||!(t.f&65536)||(t.f^=se,gt(t.deps))}function _t(e,t,n){e.f&2048?t.add(e):e.f&4096&&n.add(e),gt(e.deps),O(e,g)}var vt=[];function yt(e,t=p){let n=null,r=new Set;function i(t){if(Ze(e,t)&&(e=t,n)){let t=!vt.length;for(let t of r)t[1](),vt.push(t,e);if(t){for(let e=0;e{r.delete(c),r.size===0&&n&&(n(),n=null)}}return{set:i,update:a,subscribe:o}}var bt=!1;function xt(e){var t=bt;try{return bt=!1,[e(),bt]}finally{bt=t}}function St(e,t){if(t){let t=document.body;e.autofocus=!0,E(()=>{document.activeElement===t&&e.focus()})}}var Ct=!1;function wt(){Ct||(Ct=!0,document.addEventListener(`reset`,e=>{Promise.resolve().then(()=>{if(!e.defaultPrevented)for(let t of e.target.elements)t[ve]?.()})},{capture:!0}))}function Tt(e){var t=H,n=G;W(null),K(null);try{return e()}finally{W(t),K(n)}}function Et(e,t,n,r=n){e.addEventListener(t,()=>Tt(n));let i=e[ve];e[ve]=i?()=>{i(),r(!0)}:()=>r(!0),wt()}function Dt(e){let t=0,n=pn(0),r;return()=>{Pn()&&(Z(n),Hn(()=>(t===0&&(r=Q(()=>e(()=>_n(n)))),t+=1,()=>{E(()=>{--t,t===0&&(r?.(),r=void 0,_n(n))})})))}}var Ot=re|ae;function kt(e,t,n,r){new At(e,t,n,r)}var At=class{parent;is_pending=!1;transform_error;#e;#t=x?C:null;#n;#r;#i;#a=null;#o=null;#s=null;#c=null;#l=0;#u=0;#d=!1;#f=new Set;#p=new Set;#m=null;#h=Dt(()=>(this.#m=pn(this.#l),()=>{this.#m=null}));constructor(e,t,n,r){this.#e=e,this.#n=t,this.#r=e=>{var t=G;t.b=this,t.f|=128,n(e)},this.parent=G.b,this.transform_error=r??this.parent?.transform_error??(e=>e),this.#i=Wn(()=>{if(x){let e=this.#t;Ge();let t=e.data===`[!`;if(e.data.startsWith(`[?`)){let t=JSON.parse(e.data.slice(2));this.#_(t)}else t?this.#y():this.#g()}else this.#b()},Ot),x&&(this.#e=C)}#g(){try{this.#a=z(()=>this.#r(this.#e))}catch(e){this.error(e)}}#_(e){let t=this.#n.failed,{reset:n,invoke_onerror:r}=this.#v(e);E(r),t&&(this.#s=z(()=>{t(this.#e,()=>e,()=>n)}))}#v(e){var t=!1,n=!1;let r=()=>{if(t){We();return}t=!0,n&&Re(),this.#s!==null&&Xn(this.#s,()=>{this.#s=null}),this.#S(()=>{this.#b()})};return{reset:r,invoke_onerror:()=>{try{n=!0,this.#n.onerror?.(e,r),n=!1}catch(e){D(e,this.#i&&this.#i.parent)}}}}#y(){let e=this.#n.pending;e&&(this.is_pending=!0,this.#o=z(()=>e(this.#e)),E(()=>{var e=this.#c=document.createDocumentFragment(),t=F();e.append(t),this.#a=this.#S(()=>z(()=>this.#r(t))),this.#u===0&&(this.#e.before(e),this.#c=null,Xn(this.#o,()=>{this.#o=null}),this.#x(k))}))}#b(){try{if(this.is_pending=this.has_pending_snippet(),this.#u=0,this.#l=0,this.#a=z(()=>{this.#r(this.#e)}),this.#u>0){var e=this.#c=document.createDocumentFragment();er(this.#a,e);let t=this.#n.pending;this.#o=z(()=>t(this.#e))}else this.#x(k)}catch(e){this.error(e)}}#x(e){this.is_pending=!1,e.transfer_effects(this.#f,this.#p)}defer_effect(e){_t(e,this.#f,this.#p)}is_rendered(){return!this.is_pending&&(!this.parent||this.parent.is_rendered())}has_pending_snippet(){return!!this.#n.pending}#S(e){var t=G,n=H,r=T;K(this.#i),W(this.#i),$e(this.#i.ctx);try{return j.ensure(),e()}catch(e){return pt(e),null}finally{K(t),W(n),$e(r)}}#C(e,t){if(!this.has_pending_snippet()){this.parent&&this.parent.#C(e,t);return}this.#u+=e,this.#u===0&&(this.#x(t),this.#o&&Xn(this.#o,()=>{this.#o=null}),this.#c&&=(this.#e.before(this.#c),null))}update_pending_count(e,t){this.#C(e,t),this.#l+=e,!(!this.#m||this.#d)&&(this.#d=!0,E(()=>{this.#d=!1,this.#m&&hn(this.#m,this.#l)}))}get_effect_pending(){return this.#h(),Z(this.#m)}error(e){if(!this.#n.onerror&&!this.#n.failed)throw e;k?.is_fork?(this.#a&&k.skip_effect(this.#a),this.#o&&k.skip_effect(this.#o),this.#s&&k.skip_effect(this.#s),k.oncommit(()=>{this.#w(e)})):this.#w(e)}#w(e){this.#a&&=(B(this.#a),null),this.#o&&=(B(this.#o),null),this.#s&&=(B(this.#s),null),x&&(w(this.#t),qe(),w(Je()));let t=this.#n.failed,n=e=>{let{reset:n,invoke_onerror:r}=this.#v(e);r(),t&&(this.#s=this.#S(()=>{try{return z(()=>{var r=G;r.b=this,r.f|=128,t(this.#e,()=>e,()=>n)})}catch(e){return D(e,this.#i.parent),null}}))};E(()=>{var t;try{t=this.transform_error(e)}catch(e){D(e,this.#i&&this.#i.parent);return}typeof t==`object`&&t&&typeof t.then==`function`?t.then(n,e=>D(e,this.#i&&this.#i.parent)):n(t)})}};function jt(e,t,n,r){let i=st()?Ft:zt;var a=e.filter(e=>!e.settled),o=t.map(i);if(n.length===0&&a.length===0){r(o);return}var s=G,c=Mt(),l=a.length===1?a[0].promise:a.length>1?Promise.all(a.map(e=>e.promise)):null;function u(e){if(!(s.f&16384)){c();try{r([...o,...e])}catch(e){D(e,s)}Nt()}}var d=Pt();if(n.length===0){l.then(()=>u([])).finally(d);return}function f(){Promise.all(n.map(e=>Lt(e))).then(u).catch(e=>D(e,s)).finally(d)}l?l.then(()=>{c(),f(),Nt()}):f()}function Mt(){var e=G,t=H,n=T,r=k;return function(i=!0){K(e),W(t),$e(n),i&&!(e.f&16384)&&(r?.activate(),r?.apply())}}function Nt(e=!0){K(null),W(null),$e(null),e&&k?.deactivate()}function Pt(){var e=G,t=e.b,n=k,r=!!t?.is_rendered();return t?.update_pending_count(1,n),n.increment(r,e),()=>{t?.update_pending_count(-1,n),n.decrement(r,e)}}function Ft(e){var t=2|_;return G!==null&&(G.f|=ae),{ctx:T,deps:null,effects:null,equals:Xe,f:t,fn:e,reactions:null,rv:0,v:b,wv:0,parent:G,ac:null}}var It=Symbol(`obsolete`);function Lt(e,t,n){let r=G;r===null&&we();var i=void 0,a=pn(b),o=!H,s=new Set;return Vn(()=>{var t=G,n=h();i=n.promise;try{Promise.resolve(e()).then(n.resolve,e=>{e!==ye&&n.reject(e)}).finally(Nt)}catch(e){n.reject(e),Nt()}var c=k;if(o){if(t.f&32768)var l=Pt();if(r.b?.is_rendered())c.async_deriveds.get(t)?.reject(It);else for(let e of s.values())e.reject(It);s.add(n),c.async_deriveds.set(t,n)}let u=(e,t=void 0)=>{l?.(),s.delete(n),t!==It&&(c.activate(),t?(a.f|=ue,hn(a,t)):(a.f&8388608&&(a.f^=ue),hn(a,e)),c.deactivate())};n.promise.then(u,e=>u(null,e||`unknown`))}),Fn(()=>{for(let e of s)e.reject(It)}),new Promise(e=>{function t(n){function r(){n===i?e(a):t(i)}n.then(r,r)}t(i)})}function Rt(e){let t=Ft(e);return ir(t),t}function zt(e){let t=Ft(e);return t.equals=Qe,t}function Bt(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n{t.ac.abort(ye),t.ac=null}),t.fn!==null&&(t.teardown=p),hr(t,0),Kn(t))}function Wt(e){if(e.effects!==null)for(let t of e.effects)t.teardown&&t.fn!==null&&gr(t)}var Gt=null,k=null,Kt=null,A=null,qt=null,Jt=!1,Yt=!1,Xt=null,Zt=null,Qt=0,$t=1,j=class e{id=$t++;#e=!1;linked=!0;#t=null;#n=null;async_deriveds=new Map;current=new Map;previous=new Map;#r=new Set;#i=new Set;#a=0;#o=new Map;#s=null;#c=[];#l=[];#u=new Set;#d=new Set;#f=new Map;#p=new Set;is_fork=!1;#m=!1;constructor(){Gt===null?Gt=this:(Gt.#n=this,this.#t=Gt),Gt=this}#h(){if(this.is_fork)return!0;for(let n of this.#o.keys()){for(var e=n,t=!1;e.parent!==null;){if(this.#f.has(e)){t=!0;break}e=e.parent}if(!t)return!0}return!1}skip_effect(e){this.#f.has(e)||this.#f.set(e,{d:[],m:[]}),this.#p.delete(e)}unskip_effect(e,t=e=>this.schedule(e)){var n=this.#f.get(e);if(n){this.#f.delete(e);for(var r of n.d)O(r,_),t(r);for(r of n.m)O(r,v),t(r)}this.#p.add(e)}#g(){this.#e=!0,Qt++>1e3&&(this.#x(),tn());for(let e of this.#u)this.#d.delete(e),O(e,_),this.schedule(e);for(let e of this.#d)O(e,v),this.schedule(e);let t=this.#c;this.#c=[],this.apply();var n=Xt=[],r=[],i=Zt=[];for(let e of t)try{this.#_(e,n,r)}catch(t){throw sn(e),this.#h()||this.discard(),t}if(k=null,i.length>0){var a=e.ensure();for(let e of i)a.schedule(e)}if(Xt=null,Zt=null,this.#h()){this.#b(r),this.#b(n);for(let[e,t]of this.#f)on(e,t);i.length>0&&k.#g();return}let o=this.#v();if(o){this.#b(r),this.#b(n),o.#y(this);return}this.#u.clear(),this.#d.clear();for(let e of this.#r)e(this);this.#r.clear(),Kt=this,nn(r),nn(n),Kt=null,this.#s?.resolve();var s=k;if(this.#a===0&&(this.#c.length===0||s!==null)&&this.#x(),this.#c.length>0)if(s!==null){let e=s;e.#c.push(...this.#c.filter(t=>!e.#c.includes(t)))}else s=this;s!==null&&s.#g()}#_(e,t,n){e.f^=g;for(var r=e.first;r!==null;){var i=r.f,a=!!(i&96);if(!(a&&i&1024||i&8192||this.#f.has(r))&&r.fn!==null){a?r.f^=g:i&4?t.push(r):dr(r)&&(i&16&&this.#d.add(r),gr(r));var o=r.first;if(o!==null){r=o;continue}}for(;r!==null;){var s=r.next;if(s!==null){r=s;break}r=r.parent}}}#v(){for(var e=this.#t;e!==null;){if(!e.is_fork){for(let[t,[,n]]of this.current)if(e.current.has(t)&&!n)return e}e=e.#t}return null}#y(e){for(let[t,n]of e.current)!this.previous.has(t)&&e.previous.has(t)&&this.previous.set(t,e.previous.get(t)),this.current.set(t,n);for(let[t,n]of e.async_deriveds){let e=this.async_deriveds.get(t);e&&n.promise.then(e.resolve).catch(e.reject)}e.async_deriveds.clear(),this.transfer_effects(e.#u,e.#d);let t=e=>{var n=e.reactions;if(n!==null&&!(e.f&2&&!(e.f&6144)))for(let e of n){var r=e.f;if(r&2)t(e);else{var i=e;r&4194320&&!this.async_deriveds.has(i)&&(this.#d.delete(i),O(i,_),this.schedule(i))}}};for(let e of this.current.keys())t(e);this.oncommit(()=>e.discard()),e.#x(),k=this,this.#g()}#b(e){for(var t=0;t{this.#m=!1,this.linked&&this.flush()}))}transfer_effects(e,t){for(let t of e)this.#u.add(t);for(let e of t)this.#d.add(e);e.clear(),t.clear()}oncommit(e){this.#r.add(e)}ondiscard(e){this.#i.add(e)}settled(){return(this.#s??=h()).promise}static ensure(){if(k===null){let t=k=new e;!Yt&&!Jt&&E(()=>{t.#e||t.flush()})}return k}apply(){A=null}schedule(e){if(qt=e,e.b?.is_pending&&e.f&16777228&&!(e.f&32768)){e.b.defer_effect(e);return}for(var t=e;t.parent!==null;){t=t.parent;var n=t.f;if(Xt!==null&&t===G&&(H===null||!(H.f&2)))return;if(n&96){if(!(n&1024))return;t.f^=g}}this.#c.push(t)}#x(){if(this.linked){var e=this.#t,t=this.#n;e===null||(e.#n=t),t===null?Gt=e:t.#t=e,this.linked=!1}}};function en(e){var t=Jt;Jt=!0;try{var n;for(e&&(k!==null&&!k.is_fork&&k.flush(),n=e());;){if(ft(),k===null)return n;k.flush()}}finally{Jt=t}}function tn(){try{Oe()}catch(e){D(e,qt)}}var M=null;function nn(e){var t=e.length;if(t!==0){for(var n=0;n0)){un.clear();for(let e of M){if(e.f&24576)continue;let t=[e],n=e.parent;for(;n!==null;)M.has(n)&&(M.delete(n),t.push(n)),n=n.parent;for(let e=t.length-1;e>=0;e--){let n=t[e];n.f&24576||gr(n)}}M.clear()}}M=null}}function rn(e,t){if(e.reactions!==null)for(let n of e.reactions){let e=n.f;e&2?rn(n,t):e&131072&&(O(n,_),t.add(n))}}function an(e){k.schedule(e)}function on(e,t){if(!(e.f&32&&e.f&1024)){e.f&2048?t.d.push(e):e.f&4096&&t.m.push(e),O(e,g);for(var n=e.first;n!==null;)on(n,t),n=n.next}}function sn(e){O(e,g);for(var t=e.first;t!==null;)sn(t),t=t.next}function cn(e){xe(`fork`),k!==null&&Ae();var t=j.ensure();t.is_fork=!0,A=new Map;var n=!1,r=t.settled();return en(e),{commit:async()=>{if(n){await r;return}t.linked||ke(),n=!0,t.is_fork=!1;for(var[e,[i]]of t.current)e.v=i,e.wv=ur();en(()=>{var e=new Set;for(var n of t.current.keys())rn(n,e);dn(e),gn()}),t.flush(),await r},discard:()=>{for(var e of t.current.keys())e.wv=ur();!n&&t.linked&&t.discard()}}}var ln=new Set,un=new Map;function dn(e){ln=e}var fn=!1;function pn(e,t){return{f:0,v:e,reactions:null,equals:Xe,rv:0,wv:0}}function N(e,t){let n=pn(e,t);return ir(n),n}function mn(e,t=!1,n=!0){let r=pn(e);return t||(r.equals=Qe),r}function P(e,t,n=!1){return H!==null&&(!U||H.f&131072)&&st()&&H.f&4325394&&(q===null||!q.has(e))&&Le(),hn(e,n?yn(t):t,Zt)}function hn(e,t,n=null){if(!e.equals(t)){un.set(e,V?t:e.v);var r=j.ensure();if(r.capture(e,t),e.f&2){let t=e;e.f&2048&&Vt(t),A===null&&ht(t)}e.wv=ur(),vn(e,_,n),st()&&G!==null&&G.f&1024&&!(G.f&96)&&(X===null?ar([e]):X.push(e)),!r.is_fork&&ln.size>0&&!fn&&gn()}return t}function gn(){fn=!1;for(let e of ln){e.f&1024&&O(e,v);let t;try{t=dr(e)}catch{t=!0}t&&gr(e)}ln.clear()}function _n(e){P(e,e.v+1)}function vn(e,t,n){var r=e.reactions;if(r!==null)for(var i=st(),a=r.length,o=0;o{if(cr===c)return e();var t=H,n=cr;W(null),lr(c);var r=e();return W(t),lr(n),r};return i&&r.set(`length`,N(e.length,o)),new Proxy(e,{defineProperty(e,t,n){(!(`value`in n)||n.configurable===!1||n.enumerable===!1||n.writable===!1)&&Fe();var i=r.get(t);return i===void 0?f(()=>{var e=N(n.value,o);return r.set(t,e),e}):P(i,n.value,!0),!0},deleteProperty(e,t){var n=r.get(t);if(n===void 0){if(t in e){let e=f(()=>N(b,o));r.set(t,e),_n(a)}}else P(n,b),_n(a);return!0},get(t,n,i){if(n===de)return e;var a=r.get(n),c=n in t;if(a===void 0&&(!c||s(t,n)?.writable)&&(a=f(()=>N(yn(c?t[n]:b),o)),r.set(n,a)),a!==void 0){var l=Z(a);return l===b?void 0:l}return Reflect.get(t,n,i)},getOwnPropertyDescriptor(e,t){var n=Reflect.getOwnPropertyDescriptor(e,t);if(n&&`value`in n){var i=r.get(t);i&&(n.value=Z(i))}else if(n===void 0){var a=r.get(t),o=a?.v;if(a!==void 0&&o!==b)return{enumerable:!0,configurable:!0,value:o,writable:!0}}return n},has(e,t){if(t===de)return!0;var n=r.get(t),i=n!==void 0&&n.v!==b||Reflect.has(e,t);return(n!==void 0||G!==null&&(!i||s(e,t)?.writable))&&(n===void 0&&(n=f(()=>N(i?yn(e[t]):b,o)),r.set(t,n)),Z(n)===b)?!1:i},set(e,t,n,c){var l=r.get(t),u=t in e;if(i&&t===`length`)for(var d=n;dN(b,o)),r.set(d+``,p)):P(p,b)}if(l===void 0)(!u||s(e,t)?.writable)&&(l=f(()=>N(void 0,o)),P(l,yn(n)),r.set(t,l));else{u=l.v!==b;var m=f(()=>yn(n));P(l,m)}var h=Reflect.getOwnPropertyDescriptor(e,t);if(h?.set&&h.set.call(c,n),!u){if(i&&typeof t==`string`){var g=r.get(`length`),_=Number(t);Number.isInteger(_)&&_>=g.v&&P(g,_+1)}_n(a)}return!0},ownKeys(e){Z(a);var t=Reflect.ownKeys(e).filter(e=>{var t=r.get(e);return t===void 0||t.v!==b});for(var[n,i]of r)i.v!==b&&!(n in e)&&t.push(n);return t},setPrototypeOf(){Ie()}})}var bn,xn,Sn,Cn;function wn(){if(bn===void 0){bn=window,xn=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,n=Text.prototype;Sn=s(t,`firstChild`).get,Cn=s(t,`nextSibling`).get,f(e)&&(e[he]=void 0,e[me]=null,e[ge]=void 0,e.__e=void 0),f(n)&&(n[_e]=void 0)}}function F(e=``){return document.createTextNode(e)}function I(e){return Sn.call(e)}function L(e){return Cn.call(e)}function Tn(e,t){if(!x)return I(e);var n=I(C);if(n===null)n=C.appendChild(F());else if(t&&n.nodeType!==3){var r=F();return n?.before(r),w(r),r}return t&&jn(n),w(n),n}function En(e,t=!1){if(!x){var n=I(e);return n instanceof Comment&&n.data===``?L(n):n}if(t){if(C?.nodeType!==3){var r=F();return C?.before(r),w(r),r}jn(C)}return C}function Dn(e,t=1,n=!1){let r=x?C:e;for(var i;t--;)i=r,r=L(r);if(!x)return r;if(n){if(r?.nodeType!==3){var a=F();return r===null?i?.after(a):r.before(a),w(a),a}jn(r)}return w(r),r}function On(e){e.textContent=``}function kn(){return!1}function An(e,t,n){return t==null||t===`http://www.w3.org/1999/xhtml`?n?document.createElement(e,{is:n}):document.createElement(e):n?document.createElementNS(t,e,{is:n}):document.createElementNS(t,e)}function jn(e){if(e.nodeValue.length<65536)return;let t=e.nextSibling;for(;t!==null&&t.nodeType===3;)t.remove(),e.nodeValue+=t.nodeValue,t=e.nextSibling}function Mn(e){G===null&&(H===null&&De(e),Ee()),V&&Te(e)}function Nn(e,t){var n=t.last;n===null?t.last=t.first=e:(n.next=e,e.prev=n,t.last=e)}function R(e,t){var n=G;n!==null&&n.f&8192&&(e|=y);var r={ctx:T,deps:null,nodes:null,f:e|_|512,first:null,fn:t,last:null,next:null,parent:n,b:n&&n.b,prev:null,teardown:null,wv:0,ac:null};k?.register_created_effect(r);var i=r;if(e&4)Xt===null?j.ensure().schedule(r):Xt.push(r);else if(t!==null){try{gr(r)}catch(e){throw B(r),e}i.deps===null&&i.teardown===null&&i.nodes===null&&i.first===i.last&&!(i.f&524288)&&(i=i.first,e&16&&e&65536&&i!==null&&(i.f|=re))}if(i!==null&&(i.parent=n,n!==null&&Nn(i,n),H!==null&&H.f&2&&!(e&64))){var a=H;(a.effects??=[]).push(i)}return r}function Pn(){return H!==null&&!U}function Fn(e){let t=R(8,null);return O(t,g),t.teardown=e,t}function In(e){Mn(`$effect`);var t=G.f;if(!H&&t&32&&T!==null&&!T.i){var n=T;(n.e??=[]).push(e)}else return Ln(e)}function Ln(e){return R(4|oe,e)}function Rn(e){return Mn(`$effect.pre`),R(8|oe,e)}function zn(e){j.ensure();let t=R(64|ae,e);return(e={})=>new Promise(n=>{e.outro?Xn(t,()=>{B(t),n(void 0)}):(B(t),n(void 0))})}function Bn(e){return R(4,e)}function Vn(e){return R(le|ae,e)}function Hn(e,t=0){return R(8|t,e)}function Un(e,t=[],n=[],r=[]){jt(r,t,n,t=>{R(8,()=>{e(...t.map(Z))})})}function Wn(e,t=0){return R(16|t,e)}function z(e){return R(32|ae,e)}function Gn(e){var t=e.teardown;if(t!==null){let e=V,n=H;rr(!0),W(null);try{t.call(null)}finally{rr(e),W(n)}}}function Kn(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){let e=n.ac;e!==null&&Tt(()=>{e.abort(ye)});var r=n.next;n.f&64?n.parent=null:B(n,t),n=r}}function qn(e){for(var t=e.first;t!==null;){var n=t.next;t.f&32||B(t),t=n}}function B(e,t=!0){var n=!1;(t||e.f&262144)&&e.nodes!==null&&e.nodes.end!==null&&(Jn(e.nodes.start,e.nodes.end),n=!0),e.f|=ne,Kn(e,t&&!n),hr(e,0);var r=e.nodes&&e.nodes.t;if(r!==null)for(let e of r)e.stop();Gn(e),e.f^=ne,e.f|=ee;var i=e.parent;i!==null&&i.first!==null&&Yn(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes=e.ac=e.b=null}function Jn(e,t){for(;e!==null;){var n=e===t?null:L(e);e.remove(),e=n}}function Yn(e){var t=e.parent,n=e.prev,r=e.next;n!==null&&(n.next=r),r!==null&&(r.prev=n),t!==null&&(t.first===e&&(t.first=r),t.last===e&&(t.last=n))}function Xn(e,t,n=!0){var r=[];Zn(e,r,!0);var i=()=>{n&&B(e),t&&t()},a=r.length;if(a>0){var o=()=>--a||i();for(var s of r)s.out(o)}else i()}function Zn(e,t,n){if(!(e.f&8192)){e.f^=y;var r=e.nodes&&e.nodes.t;if(r!==null)for(let e of r)(e.is_global||n)&&t.push(e);for(var i=e.first;i!==null;){var a=i.next;if(!(i.f&64)){var o=!!(i.f&65536)||!!(i.f&32)&&!!(e.f&16);Zn(i,t,o?n:!1)}i=a}}}function Qn(e){$n(e,!0)}function $n(e,t){if(e.f&8192){e.f^=y,e.f&1024||(O(e,_),j.ensure().schedule(e));for(var n=e.first;n!==null;){var r=n.next,i=!!(n.f&65536)||!!(n.f&32);$n(n,i?t:!1),n=r}var a=e.nodes&&e.nodes.t;if(a!==null)for(let e of a)(e.is_global||t)&&e.in()}}function er(e,t){if(e.nodes)for(var n=e.nodes.start,r=e.nodes.end;n!==null;){var i=n===r?null:L(n);t.append(n),n=i}}var tr=null,nr=!1,V=!1;function rr(e){V=e}var H=null,U=!1;function W(e){H=e}var G=null;function K(e){G=e}var q=null;function ir(e){H!==null&&(q??=new Set).add(e)}var J=null,Y=0,X=null;function ar(e){X=e}var or=1,sr=0,cr=sr;function lr(e){cr=e}function ur(){return++or}function dr(e){var t=e.f;if(t&2048)return!0;if(t&2&&(e.f&=~se),t&4096){for(var n=e.deps,r=n.length,i=0;ie.wv)return!0}t&512&&A===null&&O(e,g)}return!1}function fr(e,t,n=!0){var r=e.reactions;if(r!==null&&!(q!==null&&q.has(e)))for(var i=0;i{e.ac.abort(ye)}),e.ac=null);try{e.f|=ce;var u=e.fn,d=u();e.f|=te;var f=e.deps,p=k?.is_fork;if(J!==null){var m;if(p||hr(e,Y),f!==null&&Y>0)for(f.length=Y+J.length,m=0;m{s.ac.abort(ye),s.ac=null,O(s,_)}),Ut(s),hr(s,0)}}function hr(e,t){var n=e.deps;if(n!==null)for(var r=t;rn?.call(this,e))}return e.startsWith(`pointer`)||e.startsWith(`touch`)||e===`wheel`?E(()=>{t.addEventListener(e,i,r)}):t.addEventListener(e,i,r),i}function Dr(e,t,n,r,i){var a={capture:r,passive:i},o=Er(e,t,n,a);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&Fn(()=>{t.removeEventListener(e,o,a)})}function Or(e,t,n){(t[Cr]??={})[e]=n}function kr(e){for(var t=0;t{throw e});throw p}}finally{e[Cr]=t,delete e.currentTarget,W(d),K(f)}}}var Mr=globalThis?.window?.trustedTypes&&globalThis.window.trustedTypes.createPolicy(`svelte-trusted-html`,{createHTML:e=>e});function Nr(e){return Mr?.createHTML(e)??e}function Pr(e){var t=An(`template`);return t.innerHTML=Nr(e.replaceAll(``,``)),t.content}function $(e,t){var n=G;n.nodes===null&&(n.nodes={start:e,end:t,a:null,t:null})}function Fr(e,t){var n=!!(t&1),r=!!(t&2),i,a=!e.startsWith(``);return()=>{if(x)return $(C,null),C;i===void 0&&(i=Pr(a?e:``+e),n||(i=I(i)));var t=r||xn?document.importNode(i,!0):i.cloneNode(!0);if(n){var o=I(t),s=t.lastChild;$(o,s)}else $(t,t);return t}}function Ir(e=``){if(!x){var t=F(e+``);return $(t,t),t}var n=C;return n.nodeType===3?jn(n):(n.before(n=F()),w(n)),$(n,n),n}function Lr(){if(x)return $(C,null),C;var e=document.createDocumentFragment(),t=document.createComment(``),n=F();return e.append(t,n),$(t,n),e}function Rr(e,t){if(x){var n=G;(!(n.f&32768)||n.nodes.end===null)&&(n.nodes.end=C),Ge();return}e!==null&&e.before(t)}function zr(e,t){var n=t==null?``:typeof t==`object`?`${t}`:t;n!==(e[_e]??=e.nodeValue)&&(e[_e]=n,e.nodeValue=`${n}`)}function Br(e,t){return Ur(e,t)}function Vr(e,t){wn(),t.intro=t.intro??!1;let n=t.target,r=x,i=C;try{for(var a=I(n);a&&(a.nodeType!==8||a.data!==`[`);)a=L(a);if(!a)throw ze;S(!0),w(a);let r=Ur(e,{...t,anchor:a});return S(!1),r}catch(r){if(r instanceof Error&&r.message.split(` +`).some(e=>e.startsWith(`https://svelte.dev/e/`)))throw r;return r!==ze&&console.warn(`Failed to hydrate: `,r),t.recover===!1&&Me(),wn(),On(n),S(!1),Br(e,t)}finally{S(r),w(i)}}var Hr=new Map;function Ur(e,{target:t,anchor:n,props:r={},events:i,context:o,intro:s=!0,transformError:c}){wn();var l=void 0,u=zn(()=>{var s=n??t.appendChild(F());kt(s,{pending:()=>{}},t=>{at({});var n=T;if(o&&(n.c=o),i&&(r.$$events=i),x&&$(t,null),l=e(t,r)||{},x&&(G.nodes.end=C,C===null||C.nodeType!==8||C.data!==`]`))throw Ue(),ze;ot()},c);var u=new Set,d=e=>{for(var n=0;n{for(var e of u)for(let n of[t,document]){var r=Hr.get(n),i=r.get(e);--i==0?(n.removeEventListener(e,jr),r.delete(e),r.size===0&&Hr.delete(n)):r.set(e,i)}Tr.delete(d),s!==n&&s.parentNode?.removeChild(s)}});return Wr.set(l,u),l}var Wr=new WeakMap;function Gr(e,t){let n=Wr.get(e);return n?(Wr.delete(e),n(t)):Promise.resolve()}var Kr=class{anchor;#e=new Map;#t=new Map;#n=new Map;#r=new Set;#i=!0;constructor(e,t=!0){this.anchor=e,this.#i=t}#a=e=>{if(this.#e.has(e)){var t=this.#e.get(e),n=this.#t.get(t);if(n)Qn(n),this.#r.delete(t);else{var r=this.#n.get(t);r&&(Qn(r.effect),this.#t.set(t,r.effect),this.#n.delete(t),r.fragment.lastChild.remove(),this.anchor.before(r.fragment),n=r.effect)}for(let[t,n]of this.#e){if(this.#e.delete(t),t===e)break;let r=this.#n.get(n);r&&(B(r.effect),this.#n.delete(n))}for(let[e,r]of this.#t){if(e===t||this.#r.has(e))continue;let i=()=>{if(Array.from(this.#e.values()).includes(e)){var t=document.createDocumentFragment();er(r,t),t.append(F()),this.#n.set(e,{effect:r,fragment:t})}else B(r);this.#r.delete(e),this.#t.delete(e)};this.#i||!n?(this.#r.add(e),Xn(r,i,!1)):i()}}};#o=e=>{this.#e.delete(e);let t=Array.from(this.#e.values());for(let[e,n]of this.#n)t.includes(e)||(B(n.effect),this.#n.delete(e))};ensure(e,t){var n=k,r=kn();if(t&&!this.#t.has(e)&&!this.#n.has(e))if(r){var i=document.createDocumentFragment(),a=F();i.append(a),this.#n.set(e,{effect:z(()=>t(a)),fragment:i})}else this.#t.set(e,z(()=>t(this.anchor)));if(this.#e.set(n,e),r){for(let[t,r]of this.#t)t===e?n.unskip_effect(r):n.skip_effect(r);for(let[t,r]of this.#n)t===e?n.unskip_effect(r.effect):n.skip_effect(r.effect);n.oncommit(this.#a),n.ondiscard(this.#o)}else x&&(this.anchor=C),this.#a(n)}};function qr(e,t,n=!1){var r;x&&(r=C,Ge());var i=new Kr(e),a=n?re:0;function o(e,t){if(x){var n=Ye(r);if(e!==parseInt(n.substring(1))){var a=Je();w(a),i.anchor=a,S(!1),i.ensure(e,t),S(!0);return}}i.ensure(e,t)}Wn(()=>{var e=!1;t((t,n=0)=>{e=!0,o(n,t)}),e||o(-1,null)},a)}function Jr(e,t,...n){var r=new Kr(e);Wn(()=>{let e=t()??null;r.ensure(e,e&&(t=>e(t,...n)))},re)}function Yr(e){return(t,...n)=>{var r=e(...n),i;x?(i=C,Ge()):(i=I(Pr(r.render().trim())),t.before(i));let a=r.setup?.(i);$(i,i),typeof a==`function`&&Fn(a)}}function Xr(e,t,n){var r;x&&(r=C,Ge());var i=new Kr(e);Wn(()=>{var e=t()??null;if(x&&Ye(r)===`[`!=(e!==null)){var a=Je();w(a),i.anchor=a,S(!1),i.ensure(e,e&&(t=>n(t,e))),S(!0);return}i.ensure(e,e&&(t=>n(t,e)))},re)}function Zr(e,t){let n=null,r=x;var i;if(x){n=C;for(var a=I(document.head);a!==null&&(a.nodeType!==8||a.data!==e);)a=L(a);if(a===null)S(!1);else{var o=L(a);a.remove(),w(o)}}x||(i=document.head.appendChild(F()));try{Wn(()=>{var e=z(()=>t(i));e.f|=ie})}finally{r&&(S(!0),w(n))}}function Qr(e){var t,n,r=``;if(typeof e==`string`||typeof e==`number`)r+=e;else if(typeof e==`object`)if(Array.isArray(e)){var i=e.length;for(t=0;t=0;){var s=o+a;(o===0||ti.includes(r[o-1]))&&(s===r.length||ti.includes(r[s]))?r=(o===0?``:r.substring(0,o))+r.substring(s+1):o=s}}return r===``?null:r}function ri(e,t,n,r,i,a){var o=e[he];if(x||o!==n||o===void 0){var s=ni(n,r,a);(!x||s!==e.getAttribute(`class`))&&(s==null?e.removeAttribute(`class`):t?e.className=s:e.setAttribute(`class`,s)),e[he]=n}else if(a&&i!==a)for(var c in a){var l=!!a[c];(i==null||l!==!!i[c])&&e.classList.toggle(c,l)}return a}var ii=Symbol(`is custom element`),ai=Symbol(`is html`),oi=be?`link`:`LINK`;function si(e){if(x){var t=!1,n=()=>{if(!t){if(t=!0,e.hasAttribute(`value`)){var n=e.value;ci(e,`value`,null),e.value=n}if(e.hasAttribute(`checked`)){var r=e.checked;ci(e,`checked`,null),e.checked=r}}};e[ve]=n,E(n),wt()}}function ci(e,t,n,r){var i=li(e);x&&(i[t]=e.getAttribute(t),t===`src`||t===`srcset`||t===`href`&&e.nodeName===oi)||i[t]!==(i[t]=n)&&(t===`loading`&&(e[pe]=n),n==null?e.removeAttribute(t):typeof n!=`string`&&di(e).includes(t)?e[t]=n:e.setAttribute(t,n))}function li(e){return e[me]??={[ii]:e.nodeName.includes(`-`),[ai]:e.namespaceURI===Be}}var ui=new Map;function di(e){var t=e.getAttribute(`is`)||e.nodeName,n=ui.get(t);if(n)return n;ui.set(t,n=[]);for(var r,i=e,a=Element.prototype;a!==i;){for(var o in r=c(i),r)r[o].set&&o!==`innerHTML`&&o!==`textContent`&&o!==`innerText`&&n.push(o);i=d(i)}return n}function fi(e,t,n=t){var r=new WeakSet;Et(e,`input`,async i=>{var a=i?e.defaultValue:e.value;if(a=pi(e)?mi(a):a,n(a),k!==null&&r.add(k),await _r(),a!==(a=t())){var o=e.selectionStart,s=e.selectionEnd,c=e.value.length;if(e.value=a??``,s!==null){var l=e.value.length;o===s&&s===c&&l>c?(e.selectionStart=l,e.selectionEnd=l):(e.selectionStart=o,e.selectionEnd=Math.min(s,l))}}}),(x&&e.defaultValue!==e.value||Q(t)==null&&e.value)&&(n(pi(e)?mi(e.value):e.value),k!==null&&r.add(k)),Hn(()=>{var n=t();if(e===document.activeElement){var i=k;if(r.has(i))return}pi(e)&&n===mi(e.value)||e.type===`date`&&!n&&!e.value||n!==e.value&&(e.value=n??``)})}function pi(e){var t=e.type;return t===`number`||t===`range`}function mi(e){return e===``?null:+e}function hi(e,t){return e===t||e?.[de]===t}function gi(e={},t,n,r){var i=T.r,a=G;return Bn(()=>{var o,s;return Hn(()=>{o=s,s=r?.()||[],Q(()=>{hi(n(...s),e)||(t(e,...s),o&&hi(n(...o),e)&&t(null,...o))})}),()=>{let r=a;for(;r!==i&&r.parent!==null&&r.parent.f&33554432;)r=r.parent;let o=()=>{s&&hi(n(...s),e)&&t(null,...s)},c=r.teardown;r.teardown=()=>{o(),c?.()}}}),e}function _i(e,t,n,r){var i=!0,a=!!(n&8),o=!!(n&16),c=r,l=!0,u=void 0,d=()=>o&&i?(u??=Ft(r),Z(u)):(l&&(l=!1,c=o?Q(r):r),c);let f;if(a){var p=de in e||fe in e;f=s(e,t)?.set??(p&&t in e?n=>e[t]=n:void 0)}var m,h=!1;a?[m,h]=xt(()=>e[t]):m=e[t],m===void 0&&r!==void 0&&(m=d(),f&&(i&&Pe(t),f(m)));var g=i?()=>{var n=e[t];return n===void 0?d():(l=!0,n)}:()=>{var n=e[t];return n!==void 0&&(c=void 0),n===void 0?c:n};if(i&&!(n&4))return g;if(f){var _=e.$$legacy;return(function(e,t){return arguments.length>0?((!i||!t||_||h)&&f(t?g():e),e):g()})}var v=!1,y=(n&1?Ft:zt)(()=>(v=!1,g()));a&&Z(y);var ee=G;return(function(e,t){if(arguments.length>0){let n=t?Z(y):i&&a?yn(e):e;return P(y,n),v=!0,c!==void 0&&(c=n),e}return V&&v||ee.f&16384?y.v:Z(y)})}function vi(e){return class extends yi{constructor(t){super({component:e,...t})}}}var yi=class{#e;#t;constructor(e){var t=new Map,n=(e,n)=>{var r=mn(n,!1,!1);return t.set(e,r),r};let r=new Proxy({...e.props||{},$$events:{}},{get(e,r){return Z(t.get(r)??n(r,Reflect.get(e,r)))},has(e,r){return r===fe||(Z(t.get(r)??n(r,Reflect.get(e,r))),Reflect.has(e,r))},set(e,r,i){return P(t.get(r)??n(r,i),i),Reflect.set(e,r,i)}});this.#t=(e.hydrate?Vr:Br)(e.component,{target:e.target,anchor:e.anchor,props:r,context:e.context,intro:e.intro??!1,recover:e.recover,transformError:e.transformError}),(!e?.props?.$$host||e.sync===!1)&&en(),this.#e=r.$$events;for(let e of Object.keys(this.#t))e!==`$set`&&e!==`$destroy`&&e!==`$on`&&o(this,e,{get(){return this.#t[e]},set(t){this.#t[e]=t},enumerable:!0});this.#t.$set=e=>{Object.assign(r,e)},this.#t.$destroy=()=>{Gr(this.#t)}}$set(e){this.#t.$set(e)}$on(e,t){this.#e[e]=this.#e[e]||[];let n=(...e)=>t.call(this,...e);return this.#e[e].push(n),()=>{this.#e[e]=this.#e[e].filter(e=>e!==n)}}$destroy(){this.#t.$destroy()}};function bi(e,t){if(xe(`hydratable`),x){let t=window.__svelte?.h;if(t?.has(e))return t.get(e);He(e)}return t()}var xi=t({afterUpdate:()=>Oi,beforeUpdate:()=>Di,createContext:()=>et,createEventDispatcher:()=>Ei,createRawSnippet:()=>Yr,flushSync:()=>en,fork:()=>cn,getAbortSignal:()=>Si,getAllContexts:()=>it,getContext:()=>tt,hasContext:()=>rt,hydratable:()=>bi,hydrate:()=>Vr,mount:()=>Br,onDestroy:()=>wi,onMount:()=>Ci,setContext:()=>nt,settled:()=>vr,tick:()=>_r,unmount:()=>Gr,untrack:()=>Q});function Si(){return H===null&&je(),(H.ac??=new AbortController).signal}function Ci(e){T===null&&Se(`onMount`),In(()=>{let t=Q(e);if(typeof t==`function`)return t})}function wi(e){T===null&&Se(`onDestroy`),Ci(()=>()=>Q(e))}function Ti(e,t,{bubbles:n=!1,cancelable:r=!1}={}){return new CustomEvent(e,{detail:t,bubbles:n,cancelable:r})}function Ei(){let e=T;return e===null&&Se(`createEventDispatcher`),(t,r,i)=>{let a=e.s.$$events?.[t];if(a){let o=n(a)?a.slice():[a],s=Ti(t,r,i);for(let t of o)t.call(e.x,s);return!s.defaultPrevented}return!0}}function Di(e){T===null&&Se(`beforeUpdate`),T.l===null&&Ne(`beforeUpdate`),ki(T).b.push(e)}function Oi(e){T===null&&Se(`afterUpdate`),T.l===null&&Ne(`afterUpdate`),ki(T).a.push(e)}function ki(e){var t=e.l;return t.u??={a:[],b:[],m:[]}}export{bn as A,tt as B,Z as C,Un as D,Q as E,P as F,Ke as G,at as H,N as I,t as K,Rt as L,En as M,Dn as N,In as O,yn as P,St as R,Dr as S,_r as T,nt as U,ot as V,qe as W,Lr as _,gi as a,kr as b,ci as c,Zr as d,Xr as f,Rr as g,zr as h,_i as i,Tn as j,Rn as k,ri as l,qr as m,Ci as n,fi as o,Jr as p,vi as r,si as s,xi as t,ei as u,Fr as v,vr as w,Or as x,Ir as y,yt as z}; \ No newline at end of file diff --git a/backend/internal/http/web/_app/immutable/chunks/Bjy-W4x2.js b/backend/internal/http/web/_app/immutable/chunks/Bjy-W4x2.js new file mode 100644 index 0000000..84c97e7 --- /dev/null +++ b/backend/internal/http/web/_app/immutable/chunks/Bjy-W4x2.js @@ -0,0 +1,81 @@ +var e=({status:e,message:t})=>` + + + + `+t+` + + + + +
+ `+e+` +
+

`+t+`

+
+
+ + +`;export{e as default}; \ No newline at end of file diff --git a/backend/internal/http/web/_app/immutable/chunks/Bthvo3p_.js b/backend/internal/http/web/_app/immutable/chunks/Bthvo3p_.js new file mode 100644 index 0000000..5c2531d --- /dev/null +++ b/backend/internal/http/web/_app/immutable/chunks/Bthvo3p_.js @@ -0,0 +1 @@ +import"./tTEyPFub.js"; \ No newline at end of file diff --git a/backend/internal/http/web/_app/immutable/chunks/bT040zHf.js b/backend/internal/http/web/_app/immutable/chunks/bT040zHf.js new file mode 100644 index 0000000..2cbf6ea --- /dev/null +++ b/backend/internal/http/web/_app/immutable/chunks/bT040zHf.js @@ -0,0 +1 @@ +var e=`data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANQAAAAmCAYAAAC8hLUKAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAABaBJREFUeJztnV1oHFUUx//n7ibt7mxChVRtfRKKDxZjtVpEKa0ULba7WysoVfyoKNT6JEoRRVDxAwRbXwqCffGDIGgpdXdR0KqtilK1tlFRY0GpVRttjJrsbJsmc48PSXdmtpuZvTuzZFPP7+2ePff8z8zOP9z52hAzQxCEeFAz3YAgnE0k6a3yxSDaGLnSBL3AN6WPRW9JmCmoUFkI8AM14a2ct/6YkYZaABUqVwB8ixuAwznrkbjqJ0FqEYi3RK7UyX0AxFCzm/NA8B8Lil8FcNYYCkBvzTY6AGIzlCz5BCFGxFCCECPJaeKHAWijShoLqDQ6Vh2P0zCvz/xpUoLeGe6G07HQF8x2DTAglyKFWUF9Qznjy/jGef+YFKKi3Q9WvdVAAtsAPGTUjTMnC0afL1aChSwqRnUEYYaQJZ8gxIgYShBiRAwlCDEy3UWJmWGc96AD1/piB3AS2RnqRxAMaStDTV0V9F8ZFDMJswhZ8glCjIihBCFG6i/5kp0bqGQb3vuhTwDud4f4MnRGyV4F4ILApBNWH98Mx6yXGp2C3QuFJVFqgMGcs14L1NlVWYBOvs6sMI1wNr3baMZeJFG2bzPTqVeIv+C1me+NphTLi0G0NDhLvc/Z1G9mdU9cBdIXmcyBwte8xjoUWLdkXw/gfDeAq2tTqGTfGaijcYzz1nuNtFTfUMwvNjLZ3xYv4azVH57oQWMLCKsDc1LYCUS8satoPZifiFRjkkBDoZMvAeMVs5J8GICRoTAylAKlDHXqSdODAIwMBaa1AJ4LScoBMDIU4NwDpnvNpvCzAAINBcajAFYEZKjQ70zhQwANGUqWfIIQI2IoQYgRMZQgxEj9c6gJ3YNzuv6ddla50gvmA5HVD1prsMJj6hF7AyjkPKUBqGj/AGBRNcD8NLozHZGKHj/ziXcq2S+Dcburgz3otsx0yqOLqGhP+GJaXcPrUvtdncp9YN7uCqdG0WWoU4+VDVzs0XSIirY7VtiKTIj2qL2LirZ7Xkh4k7PWrd4UKto2gDnVAKvN6E5vbrDzKZ3KwzX77ijnrAt9OSetVZgPqo7LlY1g3uHJcNBlzQ3U2Qfd6P3Q+oYix+GVmKj7GQAqcaSrbqfhx6HheU2EStCxvKhBlARzwh2DgranaRgJAF4dZapDb0PD8dQAAOU5AACAtQLIk0PJlmxPffy96fB9SSWoqX1zOpKok5asrW2874o1/fGZx3PtFWIqQNfs3XDdlY33JEs+QYgRMZQgxEhzz/J1OEdxKrnJH+S7qVBOV4cK73I2szNKc0IbwPwkSP1eHSsKvu/TvNAdVChfaTSF6LLW9NI8TRmKV3cPA3jJG6Oi3Q8i941djVEAYqjZTgI7eW3625brEJYDtLzlOi1GlnyCECNiKEGIkbZ6H0r4X3MUjL8iVSAMxtRL04ihhPaA6SnOp3eEJ7Y3suQThBgRQwlCjIihBCFG2v8ciu1PqeB5wk/xM6E3jB2dByn3wUtFeSrYByP1QWDOWZf7YonEY5jQ2zzNLjbWITUIJn9dlRrw5/Ab0Ooz93OaG3l7AEDhec5afeGJLYCwDJrcP+iK74rhOxrknHVDYM6cid0YS7o6CahQXeLPOZfZFJgzRfsbCrjU/zCj6gmbwOsy33nHVKrkQRFfga+ns2buEQBHXB17PthY5zDn04FfKGe7hgAMVXUKQ12gVPTtYZwbuUaz0jVvd1OxfD+Iov5Mwa+hKZMPJQxXdfciiVE7WJfo70ZbkCWfIMSIGEoQYiSJhP4JrLb7ouPzxqbJnx7C6wB95Bl/bF6DBgBsD8zRzjfGdTXvh6LgumFwI29q6V9ACTMdDaN/+QMA6Og5BacSbXsmxf1LzQ46Dqdm/4+R+c1WpiIIP3sCX4VPog9AdNJYy1dCN7w0q7IPGktDjg3WPzZa7j+UbbXRpuZZaAAAAABJRU5ErkJggg==`;export{e as t}; \ No newline at end of file diff --git a/backend/internal/http/web/_app/immutable/chunks/kaCwo2dy.js b/backend/internal/http/web/_app/immutable/chunks/kaCwo2dy.js new file mode 100644 index 0000000..60fd60f --- /dev/null +++ b/backend/internal/http/web/_app/immutable/chunks/kaCwo2dy.js @@ -0,0 +1 @@ +import{P as e}from"./BXe04aPf.js";var t=e({user:null,accessToken:null,status:`loading`}),n=()=>{t.status=`unauthenticated`};export{n,t}; \ No newline at end of file diff --git a/backend/internal/http/web/_app/immutable/chunks/tTEyPFub.js b/backend/internal/http/web/_app/immutable/chunks/tTEyPFub.js new file mode 100644 index 0000000..c922ff2 --- /dev/null +++ b/backend/internal/http/web/_app/immutable/chunks/tTEyPFub.js @@ -0,0 +1 @@ +import{C as e,F as t,I as n,T as r,n as i,t as a,w as o,z as s}from"./BXe04aPf.js";var c=class{constructor(e,t){this.status=e,this.body=typeof t==`string`?{message:t}:t||{message:`Error: ${e}`}}toString(){return JSON.stringify(this.body)}},l=class{constructor(e,t){try{new Headers({location:t})}catch{throw Error(`Invalid redirect location ${JSON.stringify(t)}: this string contains characters that cannot be used in HTTP headers`)}this.status=e,this.location=t}},u=class extends Error{constructor(e,t,n){super(n),this.status=e,this.text=t}};new URL(`sveltekit-internal://`);function d(e,t){return e===`/`||t===`ignore`?e:t===`never`?e.endsWith(`/`)?e.slice(0,-1):e:t===`always`&&!e.endsWith(`/`)?e+`/`:e}function f(e){return e.split(`%25`).map(decodeURI).join(`%25`)}function p(e){for(let t in e)e[t]=decodeURIComponent(e[t]);return e}function m({href:e}){return e.split(`#`)[0]}function h(){}function g(...e){let t=5381;for(let n of e)if(typeof n==`string`){let e=n.length;for(;e;)t=t*33^n.charCodeAt(--e)}else if(ArrayBuffer.isView(n)){let e=new Uint8Array(n.buffer,n.byteOffset,n.byteLength),r=e.length;for(;r;)t=t*33^e[--r]}else throw TypeError(`value must be a string or TypedArray`);return(t>>>0).toString(36)}new TextEncoder;function _(e){let t=atob(e),n=new Uint8Array(t.length);for(let e=0;e((e instanceof Request?e.method:t?.method||`GET`)!==`GET`&&y.delete(x(e)),v(e,t));var y=new Map;function ee(e,t){let n=x(e,t),r=document.querySelector(n);if(r?.textContent){r.remove();let{body:e,...t}=JSON.parse(r.textContent);r.getAttribute(`data-b64`)!==null&&(e=_(e));let i=r.getAttribute(`data-ttl`);return i&&y.set(n,{body:e,init:t,ttl:1e3*Number(i)}),Promise.resolve(new Response(e,t))}return window.fetch(e,t)}function b(e,t,n){if(y.size>0){let t=x(e,n),r=y.get(t);if(r){if(performance.now(){let n=/^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(e);if(n)return t.push({name:n[1],matcher:n[2],optional:!1,rest:!0,chained:!0}),`(?:/([^]*))?`;let r=/^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(e);if(r)return t.push({name:r[1],matcher:r[2],optional:!0,rest:!1,chained:!0}),`(?:/([^/]+))?`;if(!e)return;let i=e.split(/\[(.+?)\](?!\])/);return`/`+i.map((e,n)=>{if(n%2){if(e.startsWith(`x+`))return se(String.fromCharCode(parseInt(e.slice(2),16)));if(e.startsWith(`u+`))return se(String.fromCharCode(...e.slice(2).split(`-`).map(e=>parseInt(e,16))));let[,r,a,o,s]=te.exec(e);return t.push({name:o,matcher:s,optional:!!r,rest:!!a,chained:a?n===1&&i[0]===``:!1}),a?`([^]*?)`:r?`([^/]*)?`:`([^/]+?)`}return se(e)}).join(``)}).join(``)}/?$`),params:t}}function ie(e){return e!==``&&!/^\([^)]+\)$/.test(e)}function ae(e){return e.slice(1).split(`/`).filter(ie)}function oe(e,t,n){let r={},i=e.slice(1),a=i.filter(e=>e!==void 0),o=0;for(let e=0;ee).join(`/`),o=0),c===void 0)if(s.rest)c=``;else continue;if(!s.matcher||n[s.matcher](c)){r[s.name]=c;let n=t[e+1],l=i[e+1];n&&!n.rest&&n.optional&&l&&s.chained&&(o=0),!n&&!l&&Object.keys(r).length===a.length&&(o=0);continue}if(s.optional&&s.chained){o++;continue}return}if(!o)return r}function se(e){return e.normalize().replace(/[[\]]/g,`\\$&`).replace(/%/g,`%25`).replace(/\//g,`%2[Ff]`).replace(/\?/g,`%3[Ff]`).replace(/#/g,`%23`).replace(/[.*+?^${}()|\\]/g,`\\$&`)}function ce({nodes:e,server_loads:t,dictionary:n,matchers:r}){let i=new Set(t);return Object.entries(n).map(([t,[n,i,s]])=>{let{pattern:c,params:l}=re(t),u={id:t,exec:e=>{let t=c.exec(e);if(t)return oe(t,l,r)},errors:[1,...s||[]].map(t=>e[t]),layouts:[0,...i||[]].map(o),leaf:a(n)};return u.errors.length=u.layouts.length=Math.max(u.errors.length,u.layouts.length),u});function a(t){let n=t<0;return n&&(t=~t),[n,e[t]]}function o(t){return t===void 0?t:[i.has(t),e[t]]}}function le(e,t=JSON.parse){try{return t(sessionStorage[e])}catch{}}function ue(e,t,n=JSON.stringify){let r=n(t);try{sessionStorage[e]=r}catch{}}var S=globalThis.__sveltekit_1tkqncq?.base??``,de=globalThis.__sveltekit_1tkqncq?.assets??S??``,fe=`1785917155183`,pe=`sveltekit:snapshot`,me=`sveltekit:scroll`,he=`sveltekit:states`,C=`sveltekit:history`,w=`sveltekit:navigation`,T={tap:1,hover:2,viewport:3,eager:4,off:-1,false:-1},ge=location.origin;function _e(e){if(e instanceof URL)return e;let t=document.baseURI;if(!t){let e=document.getElementsByTagName(`base`);t=e.length?e[0].href:document.URL}return new URL(e,t)}function E(){return{x:pageXOffset,y:pageYOffset}}function D(e,t){return e.getAttribute(`data-sveltekit-${t}`)}var ve={...T,"":T.hover};function ye(e){let t=e.assignedSlot??e.parentNode;return t?.nodeType===11&&(t=t.host),t}function be(e,t){for(;e&&e!==t;){if(e.nodeName.toUpperCase()===`A`&&e.hasAttribute(`href`))return e;e=ye(e)}}function xe(e,t,n){let r;try{if(r=new URL(e instanceof SVGAElement?e.href.baseVal:e.href,document.baseURI),n&&r.hash.match(/^#[^/]/)){let e=location.hash.split(`#`)[1]||`/`;r.hash=`#${e}${r.hash}`}}catch{}let i=e instanceof SVGAElement?e.target.baseVal:e.target,a=!r||!!i||k(r,t,n)||(e.getAttribute(`rel`)||``).split(/\s+/).includes(`external`),o=r?.origin===ge&&e.hasAttribute(`download`);return{url:r,external:a,target:i,download:o}}function O(e){let t=null,n=null,r=null,i=null,a=null,o=null,s=e;for(;s&&s!==document.documentElement;)r===null&&(r=D(s,`preload-code`)),i===null&&(i=D(s,`preload-data`)),t===null&&(t=D(s,`keepfocus`)),n===null&&(n=D(s,`noscroll`)),a===null&&(a=D(s,`reload`)),o===null&&(o=D(s,`replacestate`)),s=ye(s);function c(e){switch(e){case``:case`true`:return!0;case`off`:case`false`:return!1;default:return}}return{preload_code:ve[r??`off`],preload_data:ve[i??`off`],keepfocus:c(t),noscroll:c(n),reload:c(a),replace_state:c(o)}}function Se(e){let t=s(e),n=!0;function r(){n=!0,t.update(e=>e)}function i(e){n=!1,t.set(e)}function a(e){let r;return t.subscribe(t=>{(r===void 0||n&&t!==r)&&e(r=t)})}return{notify:r,set:i,subscribe:a}}var Ce={v:h};function we(){let{set:e,subscribe:t}=s(!1),n;async function r(){clearTimeout(n);try{let t=await fetch(`${de}/_app/version.json`,{headers:{pragma:`no-cache`,"cache-control":`no-cache`}});if(!t.ok)return!1;let r=(await t.json()).version!==fe;return r&&(e(!0),Ce.v(),clearTimeout(n)),r}catch{return!1}}return{subscribe:t,check:r}}function k(e,t,n){return e.origin!==ge||!e.pathname.startsWith(t)?!0:n?e.pathname!==location.pathname:!1}function Te(e){}var Ee=new Set([`load`,`prerender`,`csr`,`ssr`,`trailingSlash`,`config`]);[...Ee],[...new Set([...Ee])];function De(e){return e.filter(e=>e!=null)}function A(e,t){return e+`/`+t}function Oe(e){return e instanceof c||e instanceof u?e.status:500}function ke(e){return e instanceof u?e.text:`Internal Error`}var j,M,N,Ae=i.toString().includes(`$$`)||/function \w+\(\) \{\}/.test(i.toString()),je=`a:`;Ae?(j={data:{},form:null,error:null,params:{},route:{id:null},state:{},status:-1,url:new URL(je)},M={current:null},N={current:!1}):(j=new class{#e=n({});get data(){return e(this.#e)}set data(e){t(this.#e,e)}#t=n(null);get form(){return e(this.#t)}set form(e){t(this.#t,e)}#n=n(null);get error(){return e(this.#n)}set error(e){t(this.#n,e)}#r=n({});get params(){return e(this.#r)}set params(e){t(this.#r,e)}#i=n({id:null});get route(){return e(this.#i)}set route(e){t(this.#i,e)}#a=n({});get state(){return e(this.#a)}set state(e){t(this.#a,e)}#o=n(-1);get status(){return e(this.#o)}set status(e){t(this.#o,e)}#s=n(new URL(je));get url(){return e(this.#s)}set url(e){t(this.#s,e)}},M=new class{#e=n(null);get current(){return e(this.#e)}set current(e){t(this.#e,e)}},N=new class{#e=n(!1);get current(){return e(this.#e)}set current(e){t(this.#e,e)}},Ce.v=()=>N.current=!0);function Me(e){Object.assign(j,e)}var{onMount:Ne,tick:Pe}=a,Fe=new Set([`icon`,`shortcut icon`,`apple-touch-icon`]),P=null,F=le(`sveltekit:scroll`)??{},I=le(`sveltekit:snapshot`)??{},L={url:Se({}),page:Se({}),navigating:s(null),updated:we()};function Ie(e){F[e]=E()}function Le(e,t){let n=e+1;for(;F[n];)delete F[n],n+=1;for(n=t+1;I[n];)delete I[n],n+=1}function R(e,t=!1){return t?location.replace(e.href):location.href=e.href,new Promise(h)}async function Re(){if(`serviceWorker`in navigator){let e=await navigator.serviceWorker.getRegistration(S||`/`);e&&await e.update()}}var ze,Be,z,B,Ve,V,He={},Ue={},H=[],We=[],U=null;function Ge(){U?.fork?.then(e=>e?.discard()),U=null,Q={element:void 0,href:void 0}}var Ke=new Map,qe=new Set,Je=new Set,W=new Set,G={branch:[],error:null,url:null,nav:null},Ye=!1,Xe=!1,Ze=!0,K=!1,q=!1,Qe=!1,$e=!1,et,J,Y,X,tt=new Set,nt=new Map,rt=new Map;async function it(e,t,n){if(globalThis.__sveltekit_1tkqncq.data){let{q:e={},p:t={},l:n={},f:r={}}=globalThis.__sveltekit_1tkqncq.data;for(let t in e)He[t]=e[t];for(let e in n)He[e]=n[e];for(let e in r)He[e]=r[e];for(let e in t)Ue[e]=t[e]}document.URL!==location.href&&(location.href=location.href),V=e,await e.hooks.init?.(),ze=ce(e),B=document.documentElement,Ve=t,Be=e.nodes[0],z=e.nodes[1],Be(),z(),J=history.state?.[C],Y=history.state?.[w],J||(J=Y=Date.now(),history.replaceState({...history.state,[C]:J,[w]:Y},``));let r=F[J];function i(){r&&(history.scrollRestoration=`manual`,scrollTo(r.x,r.y))}n?(i(),await Nt(Ve,n)):(await Z({type:`enter`,url:_e(V.hash?zt(new URL(location.href)):location.href),replace_state:!0}),i()),Mt()}function at(){H.length=0,$e=!1}function ot(e){We.some(e=>e?.snapshot)&&(I[e]=We.map(e=>e?.snapshot?.capture()))}function st(e){I[e]?.forEach((e,t)=>{We[t]?.snapshot?.restore(e)})}function ct(){Ie(J),ue(me,F),ot(Y),ue(pe,I)}async function lt(e,t,n,i){let a,o;t.invalidateAll&&Ge(),await Z({type:`goto`,url:_e(e),keepfocus:t.keepFocus,noscroll:t.noScroll,replace_state:t.replaceState,state:t.state,redirect_count:n,nav_token:i,accept:()=>{if(t.invalidateAll){$e=!0,a=new Set;for(let[e,t]of nt)for(let[n,r]of t)r.resource?.reset(),a.add(A(e,n));o=new Set;for(let[e,t]of rt)for(let n of t.keys())o.add(A(e,n))}t.invalidate&&t.invalidate.forEach(jt)}}),t.invalidateAll&&r().then(r).then(()=>{for(let[e,t]of nt)for(let[n,{resource:r}]of t)a?.has(A(e,n))&&r.start();for(let[e,t]of rt)for(let[n,{resource:r}]of t)o?.has(A(e,n))&&r.reconnect()})}async function ut(e){if(e.id!==U?.id){Ge();let t={};tt.add(t),U={id:e.id,token:t,promise:bt({...e,preload:t}).then(e=>(tt.delete(t),e.type===`loaded`&&e.state.error&&Ge(),e)),fork:null}}return U.promise}async function dt(e){let t=(await wt(e,!1))?.route;t&&await Promise.all([...t.layouts,t.leaf].filter(Boolean).map(e=>e[1]()))}async function ft(e,t,n){let r={params:G.params,route:{id:G.route?.id??null},url:new URL(location.href)};if(G={...e.state,nav:r},Me(e.props.page),et=new V.root({target:t,props:{...e.props,stores:L,components:We},hydrate:n,sync:!1,transformError:void 0}),await Promise.resolve(),n){let e={from:null,to:{...r,scroll:F[J]??E()},willUnload:!1,type:`enter`,complete:Promise.resolve()};W.forEach(t=>t(e))}st(Y),Xe=!0}async function pt({url:e,params:t,branch:n,errors:r,status:i,error:a,route:o,form:s}){let c=`never`;if(S&&(e.pathname===S||e.pathname===S+`/`))c=`always`;else for(let e of n)e?.slash!==void 0&&(c=e.slash);e.pathname=d(e.pathname,c),e.search=e.search;let l={type:`loaded`,state:{url:e,params:t,branch:n,error:a,route:o},props:{constructors:De(n).map(e=>e.node.component),page:Rt(j)}};s!==void 0&&(l.props.form=s);let u={},f=!j,p=0;for(let e=0;et(new URL(e))))return!0;return!1}function _t(e,t){return e?.type===`data`?e:e?.type===`skip`?t??null:null}function vt(e,t){if(!e)return new Set(t.searchParams.keys());let n=new Set([...e.searchParams.keys(),...t.searchParams.keys()]);for(let r of n){let i=e.searchParams.getAll(r),a=t.searchParams.getAll(r);i.every(e=>a.includes(e))&&a.every(e=>i.includes(e))&&n.delete(r)}return n}function yt({error:e,url:t,route:n,params:r}){return{type:`loaded`,state:{error:e,url:t,route:n,params:r,branch:[]},props:{page:Rt(j),constructors:[]}}}async function bt({id:e,invalidating:t,url:n,params:r,route:i,preload:a}){if(U?.id===e)return tt.delete(U.token),U.promise;let{errors:o,layouts:s,leaf:u}=i,d=[...s,u];o.forEach(e=>e?.().catch(h)),d.forEach(e=>e?.[1]().catch(h));let f=G.url?e!==Et(G.url):!1,p=G.route?i.id!==G.route.id:!1,m=vt(G.url,n),g=!1,_=d.map(async(e,t)=>{if(!e)return;let a=G.branch[t];return e[1]===a?.loader&&!gt(g,p,f,m,a.universal?.uses,r)?a:(g=!0,mt({loader:e[1],url:n,params:r,route:i,parent:async()=>{let e={};for(let n=0;nPromise.resolve({}),server_data_node:_t(null)}),{node:await z(),loader:z,universal:null,server:null,data:null}],status:e,error:t,errors:[],route:null})}catch(t){if(t instanceof l){await lt(new URL(t.location,location.href),{},0);return}let a=await V.get_error_template(),o=await $(t,{url:n,params:i,route:r}),s=a({status:e,message:String(o?.message??``).replace(/&/g,`&`).replace(//g,`>`)}),c=new DOMParser().parseFromString(s,`text/html`);throw document.documentElement.replaceChild(document.adoptNode(c.head),document.head),document.documentElement.replaceChild(document.adoptNode(c.body),document.body),t}}async function Ct(e){let t=e.href;if(Ke.has(t))return Ke.get(t);let n;try{let r=(async()=>{let t=await V.hooks.reroute({url:new URL(e),fetch:async(t,n)=>ht(t,n,e).promise})??e;if(typeof t==`string`){let n=new URL(e);V.hash?n.hash=t:n.pathname=t,t=n}return t})();Ke.set(t,r),n=await r}catch{Ke.delete(t);return}return n}async function wt(e,t){if(e&&!k(e,S,V.hash)){let n=await Ct(e);if(!n)return;let r=Tt(n);for(let n of ze){let i=n.exec(r);if(i)return{id:Et(e),invalidating:t,route:n,params:p(i),url:e}}}}function Tt(e){return f(V.hash?e.hash.replace(/^#/,``).replace(/[?#].+/,``):e.pathname.slice(S.length))||`/`}function Et(e){return(V.hash?e.hash.replace(/^#/,``):e.pathname)+e.search}function Dt({url:e,type:t,intent:n,delta:r,event:i,scroll:a}){let o=!1,s=Lt(G,n,e,t,a??null);r!==void 0&&(s.navigation.delta=r),i!==void 0&&(s.navigation.event=i);let c={...s.navigation,cancel:()=>{o=!0,s.reject(Error(`navigation cancelled`))}};return K||qe.forEach(e=>e(c)),o?null:s}async function Z({type:e,url:t,popped:n,keepfocus:i,noscroll:a,replace_state:s,state:c={},redirect_count:l=0,nav_token:d={},accept:f=h,block:p=h,event:m}){let g=X;X=d;let _=await wt(t,!1),v=e===`enter`?Lt(G,_,t,e):Dt({url:t,type:e,delta:n?.delta,intent:_,scroll:n?.scroll,event:m});if(!v){p(),X===d&&(X=g);return}let y=J,ee=Y;f(),K=!0,Xe&&v.navigation.type!==`enter`&&L.navigating.set(M.current=v.navigation);let b=_&&await bt(_);if(!b){if(k(t,S,V.hash))return await R(t,s);b=await Ot(t,{id:null},await $(new u(404,`Not Found`,`Not found: ${t.pathname}`),{url:t,params:{},route:{id:null}}),404,s)}if(t=_?.url||t,X!==d){v.reject(Error(`navigation aborted`));return}if(!b)return;if(b.type===`redirect`){if(l<20){await Z({type:e,url:new URL(b.location,t),popped:n,keepfocus:i,noscroll:a,replace_state:s,state:c,redirect_count:l+1,nav_token:d}),v.fulfil(void 0);return}if(b=await St({status:500,error:await $(Error(`Redirect loop`),{url:t,params:{},route:{id:null}}),url:t,route:{id:null}}),!b)return}else if(b.props.page.status>=400&&await L.updated.check())return await Re(),await R(t,s);if(at(),Ie(y),ot(ee),b.props.page.url.pathname!==t.pathname&&(t.pathname=b.props.page.url.pathname),c=n?n.state:c,!n){let e=+!s,n={[C]:J+=e,[w]:Y+=e,[he]:c};(s?history.replaceState:history.pushState).call(history,n,``,t),s||Le(J,Y)}let x=_&&U?.id===_.id?U.fork:null;U?.fork&&!x?Ge():(U=null,Q={element:void 0,href:void 0}),b.props.page.state=c;let te;if(Xe){let e=(await Promise.all(Array.from(Je,e=>e(v.navigation)))).filter(e=>typeof e==`function`);if(e.length>0){function t(){e.forEach(e=>{W.delete(e)})}e.push(t),e.forEach(e=>{W.add(e)})}let n=v.navigation.to;G={...b.state,nav:{params:n.params,route:n.route,url:n.url}},b.props.page&&(b.props.page.url=t),!i&&document.activeElement instanceof HTMLElement&&document.activeElement!==document.body&&document.activeElement.blur();let r=x&&await x;r?te=r.commit():(P=null,et.$set(b.props),P&&Object.assign(b.props.page,P),Me(b.props.page),te=o?.()),Qe=!0}else await ft(b,Ve,!1);let{activeElement:ne}=document;if(await te,await r(),await r(),X!==d){v.reject(Error(`navigation aborted`));return}b.props.page&&P&&Object.assign(b.props.page,P);let re=null;if(Ze){let e=n?n.scroll:a?E():null;e?scrollTo(e.x,e.y):(re=t.hash&&document.getElementById(Bt(t)))?re.scrollIntoView():scrollTo(0,0)}let ie=document.activeElement!==ne&&document.activeElement!==document.body;!i&&!ie&&It(t,!re),Ze=!0,K=!1,v.fulfil(void 0),v.navigation.to&&(v.navigation.to.scroll=E()),W.forEach(e=>e(v.navigation)),e===`popstate`&&st(Y),L.navigating.set(M.current=null)}async function Ot(e,t,n,r,i){return e.origin===ge&&e.pathname===location.pathname&&!Ye?await St({status:r,error:n,url:e,route:t}):await R(e,i)}var Q={element:void 0,href:void 0};function kt(){let e,t;B.addEventListener(`mousemove`,t=>{let n=t.target;clearTimeout(e),e=setTimeout(()=>{i(n,T.hover)},20)});function n(e){e.defaultPrevented||i(e.composedPath()[0],T.tap)}B.addEventListener(`mousedown`,n),B.addEventListener(`touchstart`,n,{passive:!0});let r=new IntersectionObserver(e=>{for(let t of e)t.isIntersecting&&(dt(new URL(t.target.href)),r.unobserve(t.target))},{threshold:0});async function i(e,n){let r=be(e,B),i=r===Q.element&&r?.href===Q.href&&n>=t;if(!r||i)return;let{url:a,external:o,download:s}=xe(r,S,V.hash);if(o||s)return;let c=O(r),l=a&&Et(G.url)===Et(a);if(!(c.reload||l))if(n<=c.preload_data){Q={element:r,href:r.href},t=T.tap;let e=await wt(a,!1);if(!e)return;ut(e)}else n<=c.preload_code&&(Q={element:r,href:r.href},t=n,dt(a))}function a(){r.disconnect();for(let e of B.querySelectorAll(`a`)){let{url:t,external:n,download:i}=xe(e,S,V.hash);if(n||i)continue;let a=O(e);a.reload||(a.preload_code===T.viewport&&r.observe(e),a.preload_code===T.eager&&dt(t))}}W.add(a),a()}function $(e,t){if(e instanceof c)return e.body;let n=Oe(e),r=ke(e);return V.hooks.handleError({error:e,event:t,status:n,message:r})??{message:r}}function At(e,t={}){return e=new URL(_e(e)),e.origin===ge?lt(e,t,0):Promise.reject(Error(`goto: invalid URL`))}function jt(e){if(typeof e==`function`)H.push(e);else{let{href:t}=new URL(e,location.href);H.push(e=>e.href===t)}}function Mt(){history.scrollRestoration=`manual`,addEventListener(`beforeunload`,e=>{let t=!1;if(ct(),!K){let e=Lt(G,void 0,null,`leave`),n={...e.navigation,cancel:()=>{t=!0,e.reject(Error(`navigation cancelled`))}};qe.forEach(e=>e(n))}t?(e.preventDefault(),e.returnValue=``):history.scrollRestoration=`auto`}),addEventListener(`visibilitychange`,()=>{document.visibilityState===`hidden`&&ct()}),navigator.connection?.saveData||kt(),B.addEventListener(`click`,async t=>{if(t.button||t.which!==1||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey||t.defaultPrevented)return;let n=be(t.composedPath()[0],B);if(!n)return;let{url:r,external:i,target:a,download:o}=xe(n,S,V.hash);if(!r)return;if(a===`_parent`||a===`_top`){if(window.parent!==window)return}else if(a&&a!==`_self`)return;let s=O(n);if(!(n instanceof SVGAElement)&&r.protocol!==location.protocol&&r.protocol!==`https:`&&r.protocol!==`http:`||o)return;let[c,l]=(V.hash?r.hash.replace(/^#/,``):r.href).split(`#`),u=c===m(location);if(i||s.reload&&(!u||!l)){Dt({url:r,type:`link`,event:t})?K=!0:t.preventDefault();return}if(l!==void 0&&u){let[,i]=G.url.href.split(`#`);if(i===l){if(t.preventDefault(),l===``||l===`top`&&n.ownerDocument.getElementById(`top`)===null)scrollTo({top:0});else{let e=n.ownerDocument.getElementById(decodeURIComponent(l));e&&(e.scrollIntoView(),e.focus())}return}if(q=!0,Ie(J),e(r),!s.replace_state)return;q=!1}t.preventDefault(),await new Promise(e=>{requestAnimationFrame(()=>{setTimeout(e,0)}),setTimeout(e,100)}),await Z({type:`link`,url:r,keepfocus:s.keepfocus,noscroll:s.noscroll,replace_state:s.replace_state??r.href===location.href,event:t})}),B.addEventListener(`submit`,e=>{if(e.defaultPrevented)return;let t=HTMLFormElement.prototype.cloneNode.call(e.target),n=e.submitter;if((n?.formTarget||t.target)===`_blank`||(n?.formMethod||t.method)!==`get`)return;let r=new URL(n?.hasAttribute(`formaction`)&&n?.formAction||t.action);if(k(r,S,!1))return;let i=e.target,a=O(i);if(a.reload)return;e.preventDefault(),e.stopPropagation();let o=new FormData(i,n);r.search=new URLSearchParams(o).toString(),Z({type:`form`,url:r,keepfocus:a.keepfocus,noscroll:a.noscroll,replace_state:a.replace_state??r.href===location.href,event:e})}),addEventListener(`popstate`,async t=>{if(!Ft)if(t.state?.[`sveltekit:history`]){let n=t.state[C];if(X={},n===J)return;let r=F[n],i=t.state[`sveltekit:states`]??{},a=new URL(t.state[`sveltekit:pageurl`]??location.href),o=t.state[w],s=G.url?m(location)===m(G.url):!1;if(o===Y&&(Qe||s)){i!==j.state&&(j.state=i),e(a),F[J]=E(),r&&scrollTo(r.x,r.y),J=n;return}let c=n-J;await Z({type:`popstate`,url:a,popped:{state:i,scroll:r,delta:c},accept:()=>{J=n,Y=o},block:()=>{history.go(-c)},nav_token:X,event:t})}else q||(e(new URL(location.href)),V.hash&&location.reload())}),addEventListener(`hashchange`,()=>{q&&(q=!1,history.replaceState({...history.state,[C]:++J,[w]:Y},``,location.href))});for(let e of document.querySelectorAll(`link`))Fe.has(e.rel)&&(e.href=e.href);addEventListener(`pageshow`,e=>{e.persisted&&L.navigating.set(M.current=null)});function e(e){G.url=j.url=e,L.page.set(Rt(j)),L.page.notify()}}async function Nt(e,{status:t=200,error:n,node_ids:r,params:i,route:a,server_route:o,data:s,form:c}){Ye=!0;let u=new URL(location.href),d;({params:i={},route:a={id:null}}=await wt(u,!1)||{}),d=ze.find(({id:e})=>e===a.id);let f,p=!0;try{let e=r.map(async(t,n)=>{let r=s[n];return r?.uses&&(r.uses=Pt(r.uses)),mt({loader:V.nodes[t],url:u,params:i,route:a,parent:async()=>{let t={};for(let r=0;r{let a=history.state;Ft=!0,location.replace(new URL(`#${n}`,location.href)),history.replaceState(a,``,e),t&&scrollTo(r,i),Ft=!1})}else{let e=document.body,t=e.getAttribute(`tabindex`);e.tabIndex=-1,e.focus({preventScroll:!0,focusVisible:!1}),t===null?e.removeAttribute(`tabindex`):e.setAttribute(`tabindex`,t)}let r=getSelection();if(r&&r.type!==`None`){let e=[];for(let t=0;t{if(r.rangeCount===e.length){for(let t=0;t{a=e,o=t});return s.catch(h),{navigation:{from:{params:e.params,route:{id:e.route?.id??null},url:e.url,scroll:E()},to:n&&{params:t?.params??null,route:{id:t?.route?.id??null},url:n,scroll:i},willUnload:!t,type:r,complete:s},fulfil:a,reject:o}}function Rt(e){return{data:e.data,error:e.error,form:e.form,params:e.params,route:e.route,state:e.state,status:e.status,url:e.url}}function zt(e){let t=new URL(e);return t.hash=decodeURIComponent(e.hash),t}function Bt(e){let t;if(V.hash){let[,,n]=e.hash.split(`#`,3);t=n??``}else t=e.hash.slice(1);return decodeURIComponent(t)}export{j as a,M as i,it as n,N as o,L as r,Te as s,At as t}; \ No newline at end of file diff --git a/backend/internal/http/web/_app/immutable/chunks/xihTtKlq.js b/backend/internal/http/web/_app/immutable/chunks/xihTtKlq.js new file mode 100644 index 0000000..afdd2d0 --- /dev/null +++ b/backend/internal/http/web/_app/immutable/chunks/xihTtKlq.js @@ -0,0 +1 @@ +typeof window<`u`&&((window.__svelte??={}).v??=new Set).add(`5`); \ No newline at end of file diff --git a/backend/internal/http/web/_app/immutable/entry/app.3rrbglG1.js b/backend/internal/http/web/_app/immutable/entry/app.3rrbglG1.js new file mode 100644 index 0000000..0073d88 --- /dev/null +++ b/backend/internal/http/web/_app/immutable/entry/app.3rrbglG1.js @@ -0,0 +1,2 @@ +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["../nodes/0.DUhcgpvL.js","../chunks/BXe04aPf.js","../chunks/xihTtKlq.js","../assets/0.CHStj0oN.css","../nodes/1.Dh_qEDnK.js","../chunks/tTEyPFub.js","../nodes/2.C55SYr5i.js","../chunks/kaCwo2dy.js","../chunks/Bthvo3p_.js","../nodes/3.BZSqsgG4.js","../chunks/bT040zHf.js","../nodes/4.5uyrhTtI.js","../nodes/5.z-y4HE0j.js"])))=>i.map(i=>d[i]); +import{C as e,D as t,F as n,G as r,H as i,I as a,L as o,M as s,N as c,O as l,T as u,V as d,_ as f,a as p,f as m,g as h,h as g,i as _,j as v,k as y,m as b,n as x,r as S,v as C,y as w}from"../chunks/BXe04aPf.js";import"../chunks/xihTtKlq.js";var T=`modulepreload`,E=function(e,t){return new URL(e,t).href},D={},O=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=E(t,n),t=s(t),t in D)return;D[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:T,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},k={},A=C(`
`),j=C(` `,1);function M(S,C){i(C,!0);let T=_(C,`components`,23,()=>[]),E=_(C,`data_0`,3,null),D=_(C,`data_1`,3,null),O=_(C,`data_2`,3,null);y(()=>C.stores.page.set(C.page)),l(()=>{C.stores,C.page,C.constructors,T(),C.form,E(),D(),O(),C.stores.page.notify()});let k=a(!1),M=a(!1),N=a(null);x(()=>{let t=C.stores.page.subscribe(()=>{e(k)&&(n(M,!0),u().then(()=>{n(N,document.title||`untitled page`,!0)}))});return n(k,!0),t});let P=o(()=>C.constructors[2]);var F=j(),I=s(F),L=t=>{let n=o(()=>C.constructors[0]);var r=f(),i=s(r);m(i,()=>e(n),(t,n)=>{p(n(t,{get data(){return E()},get form(){return C.form},get params(){return C.page.params},children:(t,n)=>{var r=f(),i=s(r),a=t=>{let n=o(()=>C.constructors[1]);var r=f(),i=s(r);m(i,()=>e(n),(t,n)=>{p(n(t,{get data(){return D()},get form(){return C.form},get params(){return C.page.params},children:(t,n)=>{var r=f(),i=s(r);m(i,()=>e(P),(e,t)=>{p(t(e,{get data(){return O()},get form(){return C.form},get params(){return C.page.params}}),e=>T()[2]=e,()=>T()?.[2])}),h(t,r)},$$slots:{default:!0}}),e=>T()[1]=e,()=>T()?.[1])}),h(t,r)},c=t=>{let n=o(()=>C.constructors[1]);var r=f(),i=s(r);m(i,()=>e(n),(e,t)=>{p(t(e,{get data(){return D()},get form(){return C.form},get params(){return C.page.params}}),e=>T()[1]=e,()=>T()?.[1])}),h(t,r)};b(i,e=>{C.constructors[2]?e(a):e(c,-1)}),h(t,r)},$$slots:{default:!0}}),e=>T()[0]=e,()=>T()?.[0])}),h(t,r)},R=t=>{let n=o(()=>C.constructors[0]);var r=f(),i=s(r);m(i,()=>e(n),(e,t)=>{p(t(e,{get data(){return E()},get form(){return C.form},get params(){return C.page.params}}),e=>T()[0]=e,()=>T()?.[0])}),h(t,r)};b(I,e=>{C.constructors[1]?e(L):e(R,-1)});var z=c(I,2),B=n=>{var i=A(),a=v(i),o=n=>{var r=w();t(()=>g(r,e(N))),h(n,r)};b(a,t=>{e(M)&&t(o)}),r(i),h(n,i)};b(z,t=>{e(k)&&t(B)}),h(S,F),d()}var N=S(M),P=[()=>O(()=>import(`../nodes/0.DUhcgpvL.js`),__vite__mapDeps([0,1,2,3]),import.meta.url),()=>O(()=>import(`../nodes/1.Dh_qEDnK.js`),__vite__mapDeps([4,1,5,2]),import.meta.url),()=>O(()=>import(`../nodes/2.C55SYr5i.js`),__vite__mapDeps([6,1,5,2,7,8]),import.meta.url),()=>O(()=>import(`../nodes/3.BZSqsgG4.js`),__vite__mapDeps([9,1,2,7,10]),import.meta.url),()=>O(()=>import(`../nodes/4.5uyrhTtI.js`),__vite__mapDeps([11,1,5,2,7,8,10]),import.meta.url),()=>O(()=>import(`../nodes/5.z-y4HE0j.js`),__vite__mapDeps([12,1,5,2,7,8,10]),import.meta.url)],F=[],I={"/(authenticated)":[3,[2]],"/(public)/login":[4],"/(public)/register":[5]},L={handleError:(({error:e})=>{console.error(e)}),reroute:(()=>{}),transport:{}},R=Object.fromEntries(Object.entries(L.transport).map(([e,t])=>[e,t.decode])),z=Object.fromEntries(Object.entries(L.transport).map(([e,t])=>[e,t.encode])),B=!1,V=(e,t)=>R[e](t),H=()=>O(()=>import(`../chunks/Bjy-W4x2.js`).then(e=>e.default),[],import.meta.url);export{V as decode,R as decoders,I as dictionary,z as encoders,H as get_error_template,B as hash,L as hooks,k as matchers,P as nodes,N as root,F as server_loads}; \ No newline at end of file diff --git a/backend/internal/http/web/_app/immutable/entry/start.BDBpcwWA.js b/backend/internal/http/web/_app/immutable/entry/start.BDBpcwWA.js new file mode 100644 index 0000000..e403d23 --- /dev/null +++ b/backend/internal/http/web/_app/immutable/entry/start.BDBpcwWA.js @@ -0,0 +1 @@ +import{n as e,s as t}from"../chunks/tTEyPFub.js";export{t as load_css,e as start}; \ No newline at end of file diff --git a/backend/internal/http/web/_app/immutable/nodes/0.DUhcgpvL.js b/backend/internal/http/web/_app/immutable/nodes/0.DUhcgpvL.js new file mode 100644 index 0000000..bd8ca6f --- /dev/null +++ b/backend/internal/http/web/_app/immutable/nodes/0.DUhcgpvL.js @@ -0,0 +1 @@ +import{D as e,K as t,M as n,W as r,_ as i,c as a,d as o,g as s,p as c,v as l}from"../chunks/BXe04aPf.js";import"../chunks/xihTtKlq.js";var u=t({prerender:()=>!1,ssr:()=>!1}),d=`data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='UTF-8'%20standalone='no'?%3e%3c!--%20Created%20with%20Inkscape%20(http://www.inkscape.org/)%20--%3e%3csvg%20width='80mm'%20height='80mm'%20viewBox='0%200%2080%2080'%20version='1.1'%20id='svg1'%20inkscape:version='1.4.4%20(dcaf3e7d9e,%202026-05-05)'%20sodipodi:docname='dessin.svg'%20xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape'%20xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'%20xmlns='http://www.w3.org/2000/svg'%20xmlns:svg='http://www.w3.org/2000/svg'%3e%3csodipodi:namedview%20id='namedview1'%20pagecolor='%23ffffff'%20bordercolor='%23000000'%20borderopacity='0.25'%20inkscape:showpageshadow='2'%20inkscape:pageopacity='0.0'%20inkscape:pagecheckerboard='0'%20inkscape:deskcolor='%23d1d1d1'%20inkscape:document-units='mm'%20inkscape:zoom='0.5'%20inkscape:cx='397'%20inkscape:cy='561'%20inkscape:window-width='2560'%20inkscape:window-height='1011'%20inkscape:window-x='0'%20inkscape:window-y='0'%20inkscape:window-maximized='1'%20inkscape:current-layer='layer1'%20inkscape:export-bgcolor='%23ffffff00'%20/%3e%3cdefs%20id='defs1'%20/%3e%3cg%20inkscape:label='Calque%201'%20inkscape:groupmode='layer'%20id='layer1'%3e%3ccircle%20style='fill:%23164450;stroke-width:0.265;stroke-dasharray:none'%20id='path1'%20cy='45.644962'%20cx='23.643471'%20r='20'%20/%3e%3ccircle%20style='fill:%23216778;stroke-width:0.264583'%20id='path1-3-1-6'%20cx='34.202797'%20cy='41.673168'%20r='17.5'%20/%3e%3ccircle%20style='fill:%232c89a0;stroke-width:0.264583'%20id='path1-3'%20cx='45.844456'%20cy='38.072063'%20r='15'%20/%3e%3ccircle%20style='fill:%2337abc8;stroke-width:0.264583'%20id='path1-3-1-3'%20cx='56.873432'%20cy='34.070667'%20r='12.5'%20/%3e%3ccircle%20style='fill:%235fbcd3;stroke-width:0.264583'%20id='path1-3-1'%20cx='65.639908'%20cy='30.484972'%20r='10'%20/%3e%3c/g%3e%3c/svg%3e`,f=l(` `,1);function p(t,l){var u=i();o(`12qhfyh`,t=>{var i=f(),o=n(i);r(2),e(()=>a(o,`href`,d)),s(t,i)});var p=n(u);c(p,()=>l.children),s(t,u)}export{p as component,u as universal}; \ No newline at end of file diff --git a/backend/internal/http/web/_app/immutable/nodes/1.Dh_qEDnK.js b/backend/internal/http/web/_app/immutable/nodes/1.Dh_qEDnK.js new file mode 100644 index 0000000..b105bfc --- /dev/null +++ b/backend/internal/http/web/_app/immutable/nodes/1.Dh_qEDnK.js @@ -0,0 +1 @@ +import{D as e,G as t,H as n,M as r,N as i,V as a,g as o,h as s,j as c,v as l}from"../chunks/BXe04aPf.js";import{a as u,r as d}from"../chunks/tTEyPFub.js";import"../chunks/xihTtKlq.js";var f={get data(){return u.data},get error(){return u.error},get form(){return u.form},get params(){return u.params},get route(){return u.route},get state(){return u.state},get status(){return u.status},get url(){return u.url}};d.updated.check;var p=f,m=l(`

`,1);function h(l,u){n(u,!0);var d=m(),f=r(d),h=c(f,!0);t(f);var g=i(f,2),_=c(g,!0);t(g),e(()=>{s(h,p.status),s(_,p.error?.message)}),o(l,d),a()}export{h as component}; \ No newline at end of file diff --git a/backend/internal/http/web/_app/immutable/nodes/2.C55SYr5i.js b/backend/internal/http/web/_app/immutable/nodes/2.C55SYr5i.js new file mode 100644 index 0000000..08132d8 --- /dev/null +++ b/backend/internal/http/web/_app/immutable/nodes/2.C55SYr5i.js @@ -0,0 +1 @@ +import{H as e,K as t,M as n,O as r,V as i,_ as a,g as o,p as s}from"../chunks/BXe04aPf.js";import{t as c}from"../chunks/tTEyPFub.js";import"../chunks/xihTtKlq.js";import{t as l}from"../chunks/kaCwo2dy.js";import"../chunks/Bthvo3p_.js";var u=t({});function d(t,u){e(u,!0),r(()=>{l.status==`unauthenticated`&&c(`/login`)});var d=a(),f=n(d);s(f,()=>u.children),o(t,d),i()}export{d as component,u as universal}; \ No newline at end of file diff --git a/backend/internal/http/web/_app/immutable/nodes/3.BZSqsgG4.js b/backend/internal/http/web/_app/immutable/nodes/3.BZSqsgG4.js new file mode 100644 index 0000000..b500c1e --- /dev/null +++ b/backend/internal/http/web/_app/immutable/nodes/3.BZSqsgG4.js @@ -0,0 +1 @@ +import{A as e,D as t,G as n,H as r,M as i,N as a,P as o,S as s,V as c,W as l,b as u,c as d,g as f,h as p,i as m,j as h,l as g,m as _,n as v,u as y,v as b,x}from"../chunks/BXe04aPf.js";import"../chunks/xihTtKlq.js";import{n as S}from"../chunks/kaCwo2dy.js";import{t as C}from"../chunks/bT040zHf.js";var w=o({sidebarDesktopOpen:!0,sidebarMobileOpen:!1,menuContentVisible:!0}),T=b(` `),E=b(`
`);function D(e,i){r(i,!0);let o=m(i,`label`,3,``),s=m(i,`icon`,3,``);var l=E(),u=h(l),d=a(u,2),v=e=>{var r=T(),i=h(r,!0);n(r),t(()=>p(i,o())),f(e,r)};_(d,e=>{w.menuContentVisible&&e(v)}),n(l),t(()=>g(u,1,y([s()]))),f(e,l),c()}var O=b(`
`),k=b(`
Fabien Masson
`,1),A=b(`
`,1);function j(e,o){r(o,!0);let s=()=>{S()};var l=A(),u=i(l),p=h(u),m=e=>{var r=O(),i=h(r);n(r),t(()=>d(i,`src`,C)),f(e,r)};_(p,e=>{w.menuContentVisible&&e(m)});var g=a(p,2),v=a(g,2);n(u);var y=a(u,2),b=h(y);D(b,{label:`Conversation`,icon:`icon-chat`});var T=a(b,2);D(T,{label:`Tâches`,icon:`icon-tasks`});var E=a(T,2);D(E,{label:`Historique`,icon:`icon-history`}),D(a(E,2),{label:`Paramétrage`,icon:`icon-cog`}),n(y);var j=a(y,2),M=h(j),N=e=>{var t=k(),r=a(i(t),2),o=h(r);n(r),x(`click`,o,s),f(e,t)},P=e=>{D(e,{label:`User`,icon:`icon-user`})};_(M,e=>{w.menuContentVisible?e(N):e(P,-1)}),n(j),x(`click`,g,function(...e){o.handleMenuDesktop?.apply(this,e)}),x(`click`,v,function(...e){o.handleMenuMobile?.apply(this,e)}),f(e,l),c()}u([`click`]);var M=b(`
Contenu
`);function N(i,a){r(a,!0);let o=()=>{w.sidebarDesktopOpen=!w.sidebarDesktopOpen,w.sidebarDesktopOpen?setTimeout(()=>{w.menuContentVisible=w.sidebarDesktopOpen},200):w.menuContentVisible=w.sidebarDesktopOpen},u=()=>{w.sidebarMobileOpen=!w.sidebarMobileOpen,w.sidebarMobileOpen?setTimeout(()=>{w.menuContentVisible=w.sidebarMobileOpen},200):w.menuContentVisible=w.sidebarMobileOpen},d=()=>{w.menuContentVisible=window.innerWidth<640?w.sidebarMobileOpen:w.sidebarDesktopOpen};v(()=>{d()});var p=M();s(`resize`,e,d);var m=h(p);j(h(m),{handleMenuDesktop:o,handleMenuMobile:u}),n(m),l(2),n(p),t(()=>g(p,1,y([w.sidebarDesktopOpen?`desktopMenuOpen`:`desktopMenuClosed`,w.sidebarMobileOpen?`mobileMenuOpen`:`mobileMenuClosed`]))),f(i,p),c()}export{N as component}; \ No newline at end of file diff --git a/backend/internal/http/web/_app/immutable/nodes/4.5uyrhTtI.js b/backend/internal/http/web/_app/immutable/nodes/4.5uyrhTtI.js new file mode 100644 index 0000000..95570bd --- /dev/null +++ b/backend/internal/http/web/_app/immutable/nodes/4.5uyrhTtI.js @@ -0,0 +1 @@ +import{D as e,G as t,H as n,M as r,N as i,P as a,R as o,S as s,V as c,W as l,b as u,c as d,g as f,j as p,m,o as h,s as g,v as _,x as v}from"../chunks/BXe04aPf.js";import{t as y}from"../chunks/tTEyPFub.js";import"../chunks/xihTtKlq.js";import{t as b}from"../chunks/kaCwo2dy.js";import"../chunks/Bthvo3p_.js";import{t as x}from"../chunks/bT040zHf.js";var S=_(`
`,1),C=_(`
← Retour
`),w=_(`

Préparerez votre galet ...


`);function T(u,_){n(_,!0);let T=a({email:``,password:``,validemail:!1}),E=()=>{T.validemail=!0},D=e=>{e.preventDefault(),console.log(`formSubmitPass `+b.status),b.status=`authenticated`,console.log(`formSubmitPass après `+b.status),y(`/`)};var O=w(),k=p(O),A=p(k),j=i(A,4),M=e=>{var n=S(),a=r(n),c=i(p(a),2),u=p(c);g(u),o(u,!0),l(2),t(c),t(a),l(2),s(`submit`,a,E),h(u,()=>T.email,e=>T.email=e),f(e,n)},N=e=>{var n=C(),r=i(p(n),2),a=p(r);g(a),o(a,!0),l(2),t(r);var c=i(r,2);t(n),s(`submit`,n,D),h(a,()=>T.password,e=>T.password=e),v(`click`,c,()=>{T.validemail=!1}),f(e,n)};m(j,e=>{T.validemail?e(N,-1):e(M)}),l(4),t(k),t(O),e(()=>d(A,`src`,x)),f(u,O),c()}u([`click`]);export{T as component}; \ No newline at end of file diff --git a/backend/internal/http/web/_app/immutable/nodes/5.z-y4HE0j.js b/backend/internal/http/web/_app/immutable/nodes/5.z-y4HE0j.js new file mode 100644 index 0000000..0794501 --- /dev/null +++ b/backend/internal/http/web/_app/immutable/nodes/5.z-y4HE0j.js @@ -0,0 +1 @@ +import{D as e,G as t,H as n,N as r,P as i,R as a,S as o,V as s,W as c,c as l,g as u,j as d,o as f,s as p,v as m}from"../chunks/BXe04aPf.js";import{t as h}from"../chunks/tTEyPFub.js";import"../chunks/xihTtKlq.js";import"../chunks/kaCwo2dy.js";import"../chunks/Bthvo3p_.js";import{t as g}from"../chunks/bT040zHf.js";var _=m(`

Inscription

`);function v(m,v){n(v,!0);let y=i({email:``,firstname:``,lastname:``,password:``,verifPassword:``,validation:!1}),b=()=>{y.validation=!0,h(`/`)};var x=_(),S=d(x),C=d(S),w=r(C,4),T=r(d(w),2);p(T),a(T,!0);var E=r(T,4);p(E);var D=r(E,4);p(D);var O=r(D,4);p(O);var k=r(O,4);p(k),c(2),t(w),t(S),t(x),e(()=>l(C,`src`,g)),o(`submit`,w,b),f(T,()=>y.email,e=>y.email=e),f(E,()=>y.firstname,e=>y.firstname=e),f(D,()=>y.lastname,e=>y.lastname=e),f(O,()=>y.password,e=>y.password=e),f(k,()=>y.verifPassword,e=>y.verifPassword=e),u(m,x),s()}export{v as component}; \ No newline at end of file diff --git a/backend/internal/http/web/_app/version.json b/backend/internal/http/web/_app/version.json new file mode 100644 index 0000000..7355450 --- /dev/null +++ b/backend/internal/http/web/_app/version.json @@ -0,0 +1 @@ +{"version":"1785917155183"} \ No newline at end of file diff --git a/backend/internal/http/web/assets/fontello/LICENSE.txt b/backend/internal/http/web/assets/fontello/LICENSE.txt new file mode 100644 index 0000000..8fa3da3 --- /dev/null +++ b/backend/internal/http/web/assets/fontello/LICENSE.txt @@ -0,0 +1,12 @@ +Font license info + + +## Font Awesome + + Copyright (C) 2016 by Dave Gandy + + Author: Dave Gandy + License: SIL () + Homepage: http://fortawesome.github.com/Font-Awesome/ + + diff --git a/backend/internal/http/web/assets/fontello/README.txt b/backend/internal/http/web/assets/fontello/README.txt new file mode 100644 index 0000000..d870892 --- /dev/null +++ b/backend/internal/http/web/assets/fontello/README.txt @@ -0,0 +1,75 @@ +This webfont is generated by https://fontello.com open source project. + + +================================================================================ +Please, note, that you should obey original font licenses, used to make this +webfont pack. Details available in LICENSE.txt file. + +- Usually, it's enough to publish content of LICENSE.txt file somewhere on your + site in "About" section. + +- If your project is open-source, usually, it will be ok to make LICENSE.txt + file publicly available in your repository. + +- Fonts, used in Fontello, don't require a clickable link on your site. + But any kind of additional authors crediting is welcome. +================================================================================ + + +Comments on archive content +--------------------------- + +- /font/* - fonts in different formats + +- /css/* - different kinds of css, for all situations. Should be ok with + twitter bootstrap. Also, you can skip style and assign icon classes + directly to text elements, if you don't mind about IE7. + +- demo.html - demo file, to show your webfont content + +- LICENSE.txt - license info about source fonts, used to build your one. + +- config.json - keeps your settings. You can import it back into fontello + anytime, to continue your work + + +Why so many CSS files ? +----------------------- + +Because we like to fit all your needs :) + +- basic file, .css - is usually enough, it contains @font-face + and character code definitions + +- *-ie7.css - if you need IE7 support, but still don't wish to put char codes + directly into html + +- *-codes.css and *-ie7-codes.css - if you like to use your own @font-face + rules, but still wish to benefit from css generation. That can be very + convenient for automated asset build systems. When you need to update font - + no need to manually edit files, just override old version with archive + content. See fontello source code for examples. + +- *-embedded.css - basic css file, but with embedded WOFF font, to avoid + CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain. + We strongly recommend to resolve this issue by `Access-Control-Allow-Origin` + server headers. But if you ok with dirty hack - this file is for you. Note, + that data url moved to separate @font-face to avoid problems with + + + + + + + + + +
+

fontello font demo

+ +
+
+
+
+ icon-chat0xe800 +
+
+ icon-users0xe801 +
+
+ icon-wrench0xe802 +
+
+ icon-cog0xe803 +
+
+
+
+ icon-user0xe804 +
+
+ icon-edit0xe805 +
+
+ icon-right-big0xe806 +
+
+ icon-logout0xe807 +
+
+
+
+ icon-facebook0xf09a +
+
+ icon-tasks0xf0ae +
+
+ icon-menu0xf0c9 +
+
+ icon-github0xf113 +
+
+
+
+ icon-apple0xf179 +
+
+ icon-graduation-cap0xf19d +
+
+ icon-google0xf1a0 +
+
+ icon-paper-plane0xf1d8 +
+
+
+
+ icon-history0xf1da +
+
+
+ + + diff --git a/backend/internal/http/web/assets/fontello/font/fontello.eot b/backend/internal/http/web/assets/fontello/font/fontello.eot new file mode 100644 index 0000000..d42d502 Binary files /dev/null and b/backend/internal/http/web/assets/fontello/font/fontello.eot differ diff --git a/backend/internal/http/web/assets/fontello/font/fontello.svg b/backend/internal/http/web/assets/fontello/font/fontello.svg new file mode 100644 index 0000000..63f5b0b --- /dev/null +++ b/backend/internal/http/web/assets/fontello/font/fontello.svg @@ -0,0 +1,44 @@ + + + +Copyright (C) 2026 by original authors @ fontello.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/internal/http/web/assets/fontello/font/fontello.ttf b/backend/internal/http/web/assets/fontello/font/fontello.ttf new file mode 100644 index 0000000..34950e4 Binary files /dev/null and b/backend/internal/http/web/assets/fontello/font/fontello.ttf differ diff --git a/backend/internal/http/web/assets/fontello/font/fontello.woff b/backend/internal/http/web/assets/fontello/font/fontello.woff new file mode 100644 index 0000000..6b623a3 Binary files /dev/null and b/backend/internal/http/web/assets/fontello/font/fontello.woff differ diff --git a/backend/internal/http/web/assets/fontello/font/fontello.woff2 b/backend/internal/http/web/assets/fontello/font/fontello.woff2 new file mode 100644 index 0000000..8ac9a58 Binary files /dev/null and b/backend/internal/http/web/assets/fontello/font/fontello.woff2 differ diff --git a/backend/internal/http/web/index.html b/backend/internal/http/web/index.html new file mode 100644 index 0000000..2ca28d6 --- /dev/null +++ b/backend/internal/http/web/index.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + +
+ +
+ + diff --git a/backend/internal/http/web/robots.txt b/backend/internal/http/web/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/backend/internal/http/web/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/backend/internal/service/auth/authenticator.go b/backend/internal/service/auth/authenticator.go new file mode 100644 index 0000000..2c49837 --- /dev/null +++ b/backend/internal/service/auth/authenticator.go @@ -0,0 +1,49 @@ +package auth + +import ( + "context" + "time" + + "github.com/labstack/echo/v5" + "trankilou.fr/lassistanoque/backend/internal/domain" +) + +type Credentials struct { + Method string // "password" ou "oidc" + Email string + Password string + OIDCCode string +} + +type Registration struct { + Method string `json:"method"` // "password" ou "oidc" + Email string `json:"email"` + Firstname string `json:"firstname"` + Lastname string `json:"lastname"` + Password string `json:"password"` +} + +type Session struct { + User *domain.User + AccessToken string + RefreshToken string + ExpiresAt time.Time +} + +const ContextUserIDKey = "userID" +const ContextEmailKey = "userEmail" +const ContextNameKey = "userName" +const ContextAdminKey = "admin" + +// Interface définie ici car c'est un port propre au cas d'usage "auth" +type Authenticator interface { + Authenticate(ctx context.Context, creds Credentials) (*Session, error) + Register(ctx context.Context, registration Registration) (*Session, error) +} + +type TokenManager interface { + GenerateAccessToken(user *domain.User) (string, time.Time, error) + GenerateRefreshToken(userID string) (string, time.Time, error) + ParseAndValidate(tokenString string) (*domain.User, error) + TokenMiddleware(next echo.HandlerFunc) echo.HandlerFunc +} diff --git a/backend/internal/service/auth/service.go b/backend/internal/service/auth/service.go new file mode 100644 index 0000000..14d532d --- /dev/null +++ b/backend/internal/service/auth/service.go @@ -0,0 +1,60 @@ +package auth + +import ( + "context" + "errors" + "fmt" + + "trankilou.fr/lassistanoque/backend/internal/domain" +) + +var ( + ErrRegistrationNotAllowed = errors.New("registration not allowed") + ErrMethodUnknown = errors.New("auth method unknown") + ErrUnauthorized = errors.New("unauthorized") +) + +type Service struct { + authenticators map[string]Authenticator // "password" -> ..., "oidc" -> ... + userRepository domain.UserRepository + settingsRepository domain.SettingsRepository +} + +func NewService( + settingsRepository domain.SettingsRepository, + userRepository domain.UserRepository, + authenticators map[string]Authenticator, +) *Service { + return &Service{ + authenticators: authenticators, + userRepository: userRepository, + settingsRepository: settingsRepository, + } +} + +func (s *Service) Login(ctx context.Context, creds Credentials) (*Session, error) { + authn, ok := s.authenticators[creds.Method] + if !ok { + return nil, fmt.Errorf("auth method unsupported: %s", creds.Method) + } + return authn.Authenticate(ctx, creds) +} + +func (s *Service) Register(registration Registration) (*Session, error) { + settings, err := s.settingsRepository.GetSettings() + if err != nil { + return nil, err + } + if !settings.RegisterEnabled { + return nil, ErrRegistrationNotAllowed + } + if authenticator, ok := s.authenticators[registration.Method]; ok { + return authenticator.Register(context.Background(), registration) + } else { + return nil, ErrMethodUnknown + } +} + +func (s *Service) Status(session *Session) (*domain.User, error) { + return nil, nil +} diff --git a/backend/internal/service/user/service.go b/backend/internal/service/user/service.go new file mode 100644 index 0000000..3d593ba --- /dev/null +++ b/backend/internal/service/user/service.go @@ -0,0 +1,17 @@ +package user + +import "trankilou.fr/lassistanoque/backend/internal/domain" + +type Service struct { + repo domain.UserRepository +} + +func NewService(repo domain.UserRepository) *Service { + return &Service{ + repo, + } +} + +func (s *Service) GetUser(id string) (*domain.User, error) { + return s.repo.FindUser(id) +} diff --git a/backend/internal/utility/genid.go b/backend/internal/utility/genid.go new file mode 100644 index 0000000..18f02b2 --- /dev/null +++ b/backend/internal/utility/genid.go @@ -0,0 +1,9 @@ +package utility + +import "github.com/sixafter/nanoid" + +func GenID() string { + //21 caractères - équivalent à un UUID v4 en sécurité contre les collisions + id, _ := nanoid.New() + return id.String() +} diff --git a/backend/lassistanoque.db-wal b/backend/lassistanoque.db-wal new file mode 100644 index 0000000..8f79136 Binary files /dev/null and b/backend/lassistanoque.db-wal differ diff --git a/backend/main.go b/backend/main.go new file mode 100644 index 0000000..9498687 --- /dev/null +++ b/backend/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "trankilou.fr/lassistanoque/backend/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/backend/web b/backend/web new file mode 160000 index 0000000..fd6aeab --- /dev/null +++ b/backend/web @@ -0,0 +1 @@ +Subproject commit fd6aeabbd58a20faf8376241bf72cd0f6947fb98 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b7becbe --- /dev/null +++ b/go.mod @@ -0,0 +1,27 @@ +module trankilou.fr/lassistanoque + +go 1.26.5 + +require ( + github.com/golang-jwt/jwt/v5 v5.3.1 + github.com/golang-migrate/migrate/v4 v4.19.1 + github.com/jmoiron/sqlx v1.4.0 + github.com/joho/godotenv v1.5.1 + github.com/labstack/echo/v5 v5.3.1 + github.com/mattn/go-sqlite3 v1.14.42 + github.com/sixafter/nanoid v1.64.5 + github.com/spf13/cobra v1.10.2 + golang.org/x/crypto v0.54.0 + turso.tech/database/tursogo v0.7.2 +) + +require ( + github.com/ebitengine/purego v0.9.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/sixafter/aes-ctr-drbg v1.19.2 // indirect + github.com/sixafter/prng-chacha v1.16.5 // indirect + github.com/spf13/pflag v1.0.9 // indirect + github.com/tursodatabase/turso-go-platform-libs v0.7.2 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/time v0.15.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9507bf1 --- /dev/null +++ b/go.sum @@ -0,0 +1,63 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= +github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/labstack/echo/v5 v5.3.1 h1:75maCxkQVGualckLc/5s/ihgpH1a1Dc6AuGWNVNs6bw= +github.com/labstack/echo/v5 v5.3.1/go.mod h1:4iEGNQiPPZnkfYpNR/L6fINd3NLiGWUD5+eBotFALas= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo= +github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sixafter/aes-ctr-drbg v1.19.2 h1:+Bd1tS9/y/GapqgyyRjmrce6a5wUAQPusJXlAOh44yM= +github.com/sixafter/aes-ctr-drbg v1.19.2/go.mod h1:iOBiPPkiy5Z5cEWm2yCoqEtnkfafxNTokJLL0zOPZeQ= +github.com/sixafter/nanoid v1.64.5 h1:Aei4SU9i9I35EyJQawo9XYpzYoARGobQRU8EnBf/Pjo= +github.com/sixafter/nanoid v1.64.5/go.mod h1:+9tqjRHutec4D9mCKGvTaGSAxj1Q+eEHOvfp2WQgWa4= +github.com/sixafter/prng-chacha v1.16.5 h1:RNPMMVDq6rzBS+wDc9BAkpcKoVRhFGfBIwGRUCo5+Cc= +github.com/sixafter/prng-chacha v1.16.5/go.mod h1:fY7WtbBwx94oa5qN417ZzCLCozjQ7JDag7dr1f/v2Ss= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tursodatabase/turso-go-platform-libs v0.7.2 h1:5ysGFHJC+I39jT1CFq2TukwscyEjMJN8zAPFXTSwvFE= +github.com/tursodatabase/turso-go-platform-libs v0.7.2/go.mod h1:bo+Lpv5OYOX1gRV9L5DLKMsYxmDs56SkZwnCOLEFcxU= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +turso.tech/database/tursogo v0.7.2 h1:WhsLl67w/Kw7ACEsJaTlXIb1parIgRqUcB8AbNZsBqU= +turso.tech/database/tursogo v0.7.2/go.mod h1:tmC+H2Ot+guA9bs5oOcbbmpj1mHFTa/g7QREXG5QHq0= diff --git a/spec.md b/spec.md new file mode 100644 index 0000000..02f8901 --- /dev/null +++ b/spec.md @@ -0,0 +1,139 @@ +# Application CONCIERGE + +## Généralités + +Nom d'application: lassistanoque + +Objectif : +Le but de l'application est d'être à la fois un outil de chat, mais surtout un lieu ou l'utilisateur peut créer des agents autonomes, à la fois dans un contexte professionnel et personnel. +Fonctionnalité : +- Espace de chat +- Espace de visualisation/gestion des tâches (tâches longues ou tâches récurrente) +- Paramétrage : + - Sélection et paramétrage du/des modèles de LLM + - Outils et serveurs MCP + - Canaux de communication (en lecture et écriture) + - Paramétrage de la mémoire + +Cibles : +- Petite entreprise +- Particulier (en selfhosting) +Il doit permettre parexemple à une PME d'automatiser les réponse à son adresse de contact, faire des études de marché, sourcer des produits, codes des applications, ... + +## Technologies + +Composants +- backend : Go avec le framework Echo +- Base de données : turso +- frontend web : pure HTML 5 + tailwind + Typescript +- frontend desktop : Wails + +Style : +- Style épuré, minimaliste +- 2 thèmes fournis : light et dark +- Tailwind + +Authentification : +- multi-utilisateur +- connexion en Openid Connect ou bien par utilisateur/mot de passe + +Développe : +- le squelette du backend + app web +- le squelette de l'application Wail +- la gestion des utilisateurs +- les thèmes + +Ne développe pas tout de suite : +- L'accès aux LLM + +Simule : +- L'accès au LLM +- La création des tâches + +Le projet doit être structuré selon les règles de l'art. +Le code doit être maintenable par un développeur moyen. + +Privilégie la création d'un seul exécutable pour le serveur (avec go embed pour embarquer l'app web), et un executable pour l'app desktop. +Un mode d'installation docker-compose doit être proposée. + +## Mode Web et mode Desktop + +Le mode web et le mode desktop utilisent la même authentification. +Le mode Desktop est juste la même applicatoin que l'application web, mais davantage intégrée à l'OS (system tray, notifications, accès par touche de raccourci, ...). +Le mode Desktop se connecte soit à un serveur distant, soit lance lui-même un serveur lorsqu'aucun serveur distant n'est configuré. Dans le dernier cas, le serveur doit être tué à la fermeture de l'application. + +## Tâches + +les tâches sont créer lors de conversation de Chat. +Par exemple si l'utilisateur demande : "Regarde tous les jours sur ebay si tu trouve des timbres rares, et enregistre les annonces intéressantes dans ma base Grist (table Timbres). Envoie-moi un email de compte-rendu de tes recherches" +Les tâches peuvent aussi être modifié par Chat. +Les tâche peuvent être répétitives ou ponctuelles, longues avec Harness ou non. + +## Base de données + +Table provider : Fournisseurs de LLM +- id -> uuid +- name -> string +- provider -> string +- key -> string +- url -> string + +Table models : Modèles LLM +- id -> uuid +- provider_uuid -> uuid +- name -> string +- modelname -> string +- configuration -> json + +Table system : Configuration system +- chat_model_uuid -> uuid +- default_lang -> string +- port -> string +- register_enabled -> bool +- password_enabled -> bool +- oidc_wellknown_url -> string +- oidc_client_id -> string +- oidc_client_secret -> string + +Table users : Utilisateur +- id -> uuid +- name -> string +- picture -> blob +- ... + +Table user_adresses : Adresse de contact de l'utilisateur +- id -> uuid +- user_uuid : uuid +- type (email, telegram, whatsapp, ...) -> string + +Table channels : Canaux de communication utilisés par l'agent +- id -> uuid +- name -> string +- enabled -> boolean +- type (email, telegram, whatsapp, ...) -> string +- configuration -> json + +Table tools : Paramétrage des outils +- id -> uuid +- name -> string +- enabled -> boolean +- configuration -> json + +Table tasks : Tâches +- id -> uuid +- owner_uuid -> uuid +- model_uuid -> uuid +- name -> string +- prompt -> string +- cron -> string +- status (scheduled, done, paused, error) -> string +- next_datetime -> datetime + +Table task_history : Historique de tâche +- id -> uuid +- task_uuid -> uuid +- start_datetime -> datetime +- enb_datetime -> datetime +- prompt -> string +- log -> string +- response -> string