diff --git a/composer.json b/composer.json index 8e5a7b4..9fe247d 100644 --- a/composer.json +++ b/composer.json @@ -16,8 +16,7 @@ "vendor-dir": "lib/vendor" }, "require": { - "php": ">=8.2 <=8.5", - "symfony/mailer": "^7.0" + "php": ">=8.2 <=8.5" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 83d0658..5d836e9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,1063 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bb709c908478727cb01c6c5cf4af2552", - "packages": [ - { - "name": "doctrine/lexer", - "version": "3.0.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", - "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "doctrine/coding-standard": "^12", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^10.5", - "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^5.21" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Lexer\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/3.0.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], - "time": "2024-02-05T11:56:58+00:00" - }, - { - "name": "egulias/email-validator", - "version": "4.0.4", - "source": { - "type": "git", - "url": "https://github.com/egulias/EmailValidator.git", - "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", - "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^2.0 || ^3.0", - "php": ">=8.1", - "symfony/polyfill-intl-idn": "^1.26" - }, - "require-dev": { - "phpunit/phpunit": "^10.2", - "vimeo/psalm": "^5.12" - }, - "suggest": { - "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Egulias\\EmailValidator\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eduardo Gulias Davis" - } - ], - "description": "A library for validating emails against several RFCs", - "homepage": "https://github.com/egulias/EmailValidator", - "keywords": [ - "email", - "emailvalidation", - "emailvalidator", - "validation", - "validator" - ], - "support": { - "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/egulias", - "type": "github" - } - ], - "time": "2025-03-06T22:45:56+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/log", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" - }, - "time": "2024-09-11T13:17:53+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.6.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:21:43+00:00" - }, - { - "name": "symfony/event-dispatcher", - "version": "v7.4.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9dddcddff1ef974ad87b3708e4b442dc38b2261d", - "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/event-dispatcher-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/service-contracts": "<2.5" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/error-handler": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-10-28T09:38:46+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/event-dispatcher": "^1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:21:43+00:00" - }, - { - "name": "symfony/mailer", - "version": "v7.4.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/mailer.git", - "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", - "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", - "shasum": "" - }, - "require": { - "egulias/email-validator": "^2.1.10|^3|^4", - "php": ">=8.2", - "psr/event-dispatcher": "^1", - "psr/log": "^1|^2|^3", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/mime": "^7.2|^8.0", - "symfony/service-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<6.4", - "symfony/messenger": "<6.4", - "symfony/mime": "<6.4", - "symfony/twig-bridge": "<6.4" - }, - "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/messenger": "^6.4|^7.0|^8.0", - "symfony/twig-bridge": "^6.4|^7.0|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Mailer\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Helps sending emails", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-11-21T15:26:00+00:00" - }, - { - "name": "symfony/mime", - "version": "v7.4.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/bdb02729471be5d047a3ac4a69068748f1a6be7a", - "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" - }, - "conflict": { - "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<6.4", - "symfony/serializer": "<6.4.3|>7.0,<7.0.3" - }, - "require-dev": { - "egulias/email-validator": "^2.1.10|^3.1|^4", - "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/property-access": "^6.4|^7.0|^8.0", - "symfony/property-info": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4.3|^7.0.3|^8.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Mime\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows manipulating MIME messages", - "homepage": "https://symfony.com", - "keywords": [ - "mime", - "mime-type" - ], - "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-11-16T10:14:42+00:00" - }, - { - "name": "symfony/polyfill-intl-idn", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", - "shasum": "" - }, - "require": { - "php": ">=7.2", - "symfony/polyfill-intl-normalizer": "^1.10" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" - }, - { - "name": "Trevor Rowbotham", - "email": "trevor.rowbotham@pm.me" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "idn", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-10T14:38:51+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", - "shasum": "" - }, - "require": { - "ext-iconv": "*", - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-12-23T08:48:59+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v3.6.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-07-15T11:30:57+00:00" - } - ], + "content-hash": "af8ba5538bb10650d75e7256796d4530", + "packages": [], "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=8.2 <=8.5" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "8.2" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/lib/Module.php b/lib/Module.php index 987048e..ac5c5fc 100644 --- a/lib/Module.php +++ b/lib/Module.php @@ -8,11 +8,6 @@ use KTXF\Module\ModuleInstanceAbstract; use KTXF\Resource\Provider\ProviderInterface; use KTXM\ProviderMailSystem\Providers\Provider; -/** - * SMTP Mail Provider Module - * - * Provides outbound-only mail service via SMTP using Symfony Mailer. - */ class Module extends ModuleInstanceAbstract implements ModuleBrowserInterface { public function __construct( @@ -36,12 +31,12 @@ class Module extends ModuleInstanceAbstract implements ModuleBrowserInterface public function description(): string { - return 'System mail provider module for Ktrix - provides outbound mail delivery via SMTP'; + return 'System mail provider module for Ktrix - routes system-generated mail to configured mail services'; } public function version(): string { - return '0.0.1'; + return '0.0.2'; } public function permissions(): array @@ -54,7 +49,7 @@ class Module extends ModuleInstanceAbstract implements ModuleBrowserInterface ], ]; } - + public function boot(): void { $this->providerManager->register(ProviderInterface::TYPE_MAIL, 'system', Provider::class); @@ -67,7 +62,8 @@ class Module extends ModuleInstanceAbstract implements ModuleBrowserInterface 'version' => $this->version(), 'label' => $this->label(), 'author' => $this->author(), - 'description' => $this->description() + 'description' => $this->description(), + 'boot' => 'static/module.mjs', ]; } } diff --git a/lib/Providers/Provider.php b/lib/Providers/Provider.php index 73a117e..261b5cc 100644 --- a/lib/Providers/Provider.php +++ b/lib/Providers/Provider.php @@ -9,228 +9,254 @@ declare(strict_types=1); namespace KTXM\ProviderMailSystem\Providers; +use InvalidArgumentException; +use KTXC\Resource\ProviderManager; use KTXF\Mail\Provider\ProviderBaseInterface; -use KTXF\Mail\Selector\ServiceSelector; -use KTXF\Mail\Service\IServiceBase; -use KTXF\Mail\Service\ServiceScope; +use KTXF\Mail\Provider\ProviderServiceMutateInterface; +use KTXF\Resource\Provider\ResourceServiceMutateInterface; use KTXM\ProviderMailSystem\Stores\ServiceStore; -use Psr\Log\LoggerInterface; /** - * SMTP Mail Provider - * - * Provider for SMTP-based mail services. Supports multiple configured - * services per tenant with system and user scopes. - * - * @since 2025.05.01 + * System Mail Provider + * + * Rule-based routing provider for system-generated mail. Owns the reserved + * "@system" address namespace: each service is a route that maps a logical + * system address (e.g. authentication@system) to a backing service on a real + * mail provider, with the sender identity rewritten on submission. A + * "*@system" route acts as the tenant-wide default (catch-all). + * + * Routes are tenant scoped and resolvable only under the reserved system + * user context. + * + * @since 2026.07.01 */ -class Provider implements ProviderBaseInterface { +class Provider implements ProviderBaseInterface, ProviderServiceMutateInterface +{ - private const PROVIDER_ID = 'system'; - private const PROVIDER_LABEL = 'System Mail Provider'; - private const PROVIDER_DESCRIPTION = 'Outbound-only System mail provider for system notifications'; - private const PROVIDER_ICON = 'fa-envelope'; + protected const PROVIDER_IDENTIFIER = 'system'; + protected const PROVIDER_LABEL = 'System Mail Provider'; + protected const PROVIDER_DESCRIPTION = 'Routes system-generated mail to configured mail services'; + protected const PROVIDER_ICON = 'mdi-email-lock'; - private array $capabilities = [ - self::CAPABILITY_SERVICE_LIST => true, - self::CAPABILITY_SERVICE_FETCH => true, - self::CAPABILITY_SERVICE_EXTANT => true, + protected const ADDRESS_DOMAIN_SUFFIX = '@system'; + + protected array $providerAbilities = [ + self::CAPABILITY_SERVICE_LIST => true, + self::CAPABILITY_SERVICE_FETCH => true, + self::CAPABILITY_SERVICE_EXTANT => true, + self::CAPABILITY_SERVICE_CREATE => true, + self::CAPABILITY_SERVICE_MODIFY => true, + self::CAPABILITY_SERVICE_DESTROY => true, ]; public function __construct( - private LoggerInterface $logger, - private ServiceStore $serviceStore, + private readonly ServiceStore $serviceStore, + private readonly ProviderManager $providerManager, ) {} - /** - * @inheritDoc - */ - public function capable(string $value): bool { - return $this->capabilities[$value] ?? false; - } - - /** - * @inheritDoc - */ - public function capabilities(): array { - return $this->capabilities; - } - - /** - * @inheritDoc - */ - public function id(): string { - return self::PROVIDER_ID; - } - - /** - * @inheritDoc - */ - public function label(): string { - return self::PROVIDER_LABEL; - } - - /** - * @inheritDoc - */ - public function type(): string { - return self::TYPE_MAIL; - } - - /** - * @inheritDoc - */ - public function identifier(): string { - return self::PROVIDER_ID; - } - - /** - * @inheritDoc - */ - public function description(): string { - return self::PROVIDER_DESCRIPTION; - } - - /** - * @inheritDoc - */ - public function icon(): string { - return self::PROVIDER_ICON; - } - - /** - * @inheritDoc - */ - public function serviceList(string $tenantId, string $userId, ?ServiceSelector $selector = null): array { - - } - - /** - * @inheritDoc - */ - public function serviceExtant(string $tenantId, ?string $userId, string|int ...$identifiers): array { - $result = []; - - foreach ($identifiers as $id) { - $result[$id] = $this->serviceFetch($tenantId, $userId, $id) !== null; - } - - return $result; - } - - /** - * @inheritDoc - */ - public function serviceFetch(string $tenantId, ?string $userId, string|int $identifier): ?IServiceBase { - $identifier = (string)$identifier; - if ($identifier === '') { - return null; - } - - // Only handle @system addresses for this provider - if (!str_ends_with(strtolower($identifier), '@system')) { - return null; - } - - // Fetch by primary address (which is the sid) - $service = $this->serviceStore->getService($tenantId, $identifier); - if ($service === null) { - return null; - } - - // Enforce scope visibility - if ($service->getScope() === ServiceScope::System) { - return $service; - } - - if ($service->getScope() === ServiceScope::User) { - if ($userId !== null && $service->getOwner() === $userId) { - return $service; - } - return null; - } - - return null; - } - - /** - * @inheritDoc - */ - public function serviceFindByAddress(string $tenantId, ?string $userId, string $address): ?IServiceBase { - $address = strtolower(trim($address)); - if ($address === '') { - return null; - } - - // Only handle @system addresses for this provider - if (!str_ends_with($address, '@system')) { - return null; - } - - // Use store's findServiceByAddress which checks primary, secondary, and catch-all patterns - $service = $this->serviceStore->findServiceByAddress($tenantId, $address); - if ($service === null) { - return null; - } - - // Enforce scope visibility - if ($service->getScope() === ServiceScope::System) { - return $service; - } - - if ($service->getScope() === ServiceScope::User) { - if ($userId !== null && $service->getOwner() === $userId) { - return $service; - } - return null; - } - - return null; - } - - /** - * @inheritDoc - */ - public function jsonSerialize(): array { + public function jsonSerialize(): array + { return [ - self::JSON_PROPERTY_TYPE => self::JSON_TYPE, - self::JSON_PROPERTY_ID => $this->id(), - self::JSON_PROPERTY_LABEL => $this->label(), - self::JSON_PROPERTY_CAPABILITIES => $this->capabilities(), + self::PROPERTY_TYPE => self::JSON_TYPE, + self::PROPERTY_IDENTIFIER => self::PROVIDER_IDENTIFIER, + self::PROPERTY_LABEL => self::PROVIDER_LABEL, + self::PROPERTY_CAPABILITIES => $this->providerAbilities, ]; } + public function jsonDeserialize(array|string $data): static + { + return $this; + } + + public function type(): string + { + return self::TYPE_MAIL; + } + + public function identifier(): string + { + return self::PROVIDER_IDENTIFIER; + } + + public function label(): string + { + return self::PROVIDER_LABEL; + } + + public function description(): string + { + return self::PROVIDER_DESCRIPTION; + } + + public function icon(): string + { + return self::PROVIDER_ICON; + } + + public function capable(string $value): bool + { + return !empty($this->providerAbilities[$value]); + } + + public function capabilities(): array + { + return $this->providerAbilities; + } + + public function serviceList(string $tenantId, string $userId, array $filter = []): array + { + // routes are tenant scoped and only visible in the system user context, + // keeping them out of regular user account listings + if ($userId !== self::USER_SYSTEM) { + return []; + } + + $list = []; + foreach ($this->serviceStore->list($tenantId, $filter ?: null) as $serviceData) { + $serviceInstance = $this->serviceFresh()->fromStore($serviceData); + $list[$serviceInstance->identifier()] = $serviceInstance; + } + return $list; + } + + public function serviceFetch(string $tenantId, string $userId, string|int $identifier): ?Service + { + if ($userId !== self::USER_SYSTEM) { + return null; + } + + $serviceData = $this->serviceStore->fetch($tenantId, $identifier); + if ($serviceData === null) { + return null; + } + return $this->serviceFresh()->fromStore($serviceData); + } + /** - * Apply selector filters to services + * Finds the route that handles a logical system address + * + * Only addresses in the reserved "@system" namespace are considered, and + * only when asked in the reserved system user context — real users can + * not resolve (and therefore not send through) system routes. An exact + * address match always wins over the "*@system" catch-all. */ - private function applySelector(array $services, ServiceSelector $selector): array { - return array_filter($services, function(IServiceBase $service) use ($selector) { - if ($selector->getScope() !== null && $service->getScope() !== $selector->getScope()) { - return false; + public function serviceFindByAddress(string $tenantId, string $userId, string $address): ?Service + { + $address = strtolower(trim($address)); + if (!str_ends_with($address, self::ADDRESS_DOMAIN_SUFFIX)) { + return null; + } + if ($userId !== self::USER_SYSTEM) { + return null; + } + + $catchAll = null; + /** @var Service $service */ + foreach ($this->serviceList($tenantId, $userId) as $service) { + if (!$service->getEnabled()) { + continue; } - - if ($selector->getOwner() !== null && $service->getOwner() !== $selector->getOwner()) { - return false; + if ($service->hasAddressExact($address)) { + return $service; } - - if ($selector->getAddress() !== null && !$service->handlesAddress($selector->getAddress())) { - return false; + if ($catchAll === null && $service->hasAddressPattern($address)) { + $catchAll = $service; } - - if ($selector->getCapabilities() !== null) { - foreach ($selector->getCapabilities() as $cap) { - if (!$service->capable($cap)) { - return false; - } - } + } + + return $catchAll; + } + + public function serviceExtant(string $tenantId, string $userId, string|int ...$identifiers): array + { + if ($userId !== self::USER_SYSTEM) { + return array_fill_keys(array_map('strval', $identifiers), false); + } + + return $this->serviceStore->extant($tenantId, $identifiers); + } + + public function serviceFresh(): Service + { + return new Service($this->providerManager); + } + + public function serviceCreate(string $tenantId, string $userId, ResourceServiceMutateInterface $service): string + { + $this->assertSystemContext($userId); + if (!($service instanceof Service)) { + throw new InvalidArgumentException('Service must be an instance of System Mail Service'); + } + $this->validateRoute($service); + + $created = $this->serviceStore->create($tenantId, $service); + return (string)$created['sid']; + } + + public function serviceModify(string $tenantId, string $userId, ResourceServiceMutateInterface $service): string + { + $this->assertSystemContext($userId); + if (!($service instanceof Service)) { + throw new InvalidArgumentException('Service must be an instance of System Mail Service'); + } + $this->validateRoute($service); + + $this->serviceStore->modify($tenantId, $service); + return (string)$service->identifier(); + } + + public function serviceDestroy(string $tenantId, string $userId, ResourceServiceMutateInterface $service): bool + { + $this->assertSystemContext($userId); + if (!($service instanceof Service)) { + return false; + } + + return $this->serviceStore->delete($tenantId, $service->identifier()); + } + + /** + * Ensures the operation runs in the reserved system user context + * + * @throws InvalidArgumentException + */ + protected function assertSystemContext(string $userId): void + { + if ($userId !== self::USER_SYSTEM) { + throw new InvalidArgumentException('System mail routes can only be managed in the system user context'); + } + } + + /** + * Validates route configuration before persisting + * + * @throws InvalidArgumentException + */ + protected function validateRoute(Service $service): void + { + $address = strtolower(trim($service->getPrimaryAddress()->getAddress())); + if ($address === '' || !str_ends_with($address, self::ADDRESS_DOMAIN_SUFFIX)) { + throw new InvalidArgumentException('Route address must be within the reserved "@system" namespace (e.g. authentication@system or *@system)'); + } + foreach ($service->getSecondaryAddresses() as $secondary) { + $secondaryAddress = strtolower(trim($secondary->getAddress())); + if ($secondaryAddress === '' || !str_ends_with($secondaryAddress, self::ADDRESS_DOMAIN_SUFFIX)) { + throw new InvalidArgumentException('Route alias addresses must be within the reserved "@system" namespace'); } - - if ($selector->getEnabled() !== null && $service->getEnabled() !== $selector->getEnabled()) { - return false; - } - - return true; - }); + } + + if ($service->getTargetProvider() === '' || $service->getTargetService() === '') { + throw new InvalidArgumentException('Route requires a delivery target (provider and service)'); + } + if ($service->getTargetProvider() === self::PROVIDER_IDENTIFIER) { + throw new InvalidArgumentException('Route cannot target the system provider itself'); + } + + $fromAddress = $service->getFromAddress(); + if ($fromAddress === '' || filter_var($fromAddress, FILTER_VALIDATE_EMAIL) === false) { + throw new InvalidArgumentException('Route requires a valid sender (from) address'); + } } } diff --git a/lib/Providers/Service.php b/lib/Providers/Service.php index 60ff00f..916258e 100644 --- a/lib/Providers/Service.php +++ b/lib/Providers/Service.php @@ -9,645 +9,508 @@ declare(strict_types=1); namespace KTXM\ProviderMailSystem\Providers; -use KTXF\Mail\Entity\Address; -use KTXF\Mail\Entity\IAddress; -use KTXF\Mail\Entity\IMessageMutable; -use KTXF\Mail\Entity\Message; -use KTXF\Mail\Exception\SendException; -use KTXF\Mail\Service\IServiceIdentity; -use KTXF\Mail\Service\IServiceLocation; -use KTXF\Mail\Service\IServiceSend; -use KTXF\Mail\Service\ServiceScope; -use Symfony\Component\Mailer\Exception\TransportExceptionInterface; -use Symfony\Component\Mailer\Mailer; -use Symfony\Component\Mailer\Transport; -use Symfony\Component\Mime\Email; +use BadMethodCallException; +use Generator; +use KTXC\Resource\ProviderManager; +use KTXF\Mail\Entity\EntityMutableInterface; +use KTXF\Mail\Object\Address; +use KTXF\Mail\Object\AddressInterface; +use KTXF\Mail\Object\MessagePropertiesMutableInterface; +use KTXF\Mail\Provider\ProviderBaseInterface; +use KTXF\Mail\Service\ServiceBaseInterface; +use KTXF\Mail\Service\ServiceEntitySubmitInterface; +use KTXF\Mail\Service\ServiceMutableInterface; +use KTXF\Mail\Submission\EntitySubmitResult; +use KTXF\Resource\BinaryResource; +use KTXF\Resource\Delta\Delta; +use KTXF\Resource\Filter\IFilter; +use KTXF\Resource\Identifier\EntityIdentifierInterface; +use KTXF\Resource\Provider\ProviderInterface; +use KTXF\Resource\Provider\ResourceServiceIdentityInterface; +use KTXF\Resource\Provider\ResourceServiceLocationInterface; +use KTXF\Resource\Range\IRange; +use KTXF\Resource\Range\RangeType; +use KTXF\Resource\Sort\ISort; +use RuntimeException; /** - * SMTP Mail Service - * - * Outbound-only mail service using Symfony Mailer for SMTP delivery. - * - * @since 2025.05.01 + * System Mail Route Service + * + * A submit-only service that maps a logical system address + * (e.g. authentication@system, or the *@system catch-all) to a backing + * service on a real mail provider. Submission is delegated to the target + * service with the From identity rewritten to the configured real address. + * + * @since 2026.07.01 */ -class Service implements IServiceSend { +class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceEntitySubmitInterface +{ + public const PROVIDER_IDENTIFIER = 'system'; - private array $capabilities = [ - self::CAPABILITY_SEND => true, + // Route target configuration keys (stored in auxiliary) + public const AUX_TARGET_PROVIDER = 'targetProvider'; + public const AUX_TARGET_SERVICE = 'targetService'; + public const AUX_FROM_ADDRESS = 'fromAddress'; + public const AUX_FROM_LABEL = 'fromLabel'; + + private ?string $serviceTenantId = null; + private ?string $serviceIdentifier = null; + private ?string $serviceLabel = null; + private bool $serviceEnabled = false; + private array $primaryAddress = []; + private array $secondaryAddresses = []; + private array $auxiliary = []; + + private array $serviceAbilities = [ + self::CAPABILITY_ENTITY_SUBMIT_FRESH => true, ]; - /** @var array */ - private array $secondaryAddresses = []; - - private ?Mailer $mailer = null; - - /** - * @param string $providerId Provider identifier - * @param string|int $id Service identifier - * @param string $label Human-friendly label - * @param ServiceScope $scope Service scope - * @param string|null $owner Owner user ID (for User scope) - * @param bool $enabled Whether service is enabled - * @param IAddress|null $primaryAddress Primary sending address - * @param IServiceLocation|null $location Connection location - * @param IServiceIdentity|null $identity Authentication credentials - */ public function __construct( - private string $providerId, - private string|int $id, - private string $label, - private ServiceScope $scope = ServiceScope::System, - private ?string $owner = null, - private bool $enabled = true, - private ?IAddress $primaryAddress = null, - private ?IServiceLocation $location = null, - private ?IServiceIdentity $identity = null, + private readonly ?ProviderManager $providerManager = null, ) {} - /** - * @inheritDoc - */ - public function capable(string $value): bool { - return $this->capabilities[$value] ?? false; + public function toStore(): array + { + return array_filter([ + 'tid' => $this->serviceTenantId, + 'sid' => $this->serviceIdentifier, + 'enabled' => $this->serviceEnabled, + 'label' => $this->serviceLabel, + 'primaryAddress' => $this->primaryAddress, + 'secondaryAddresses' => $this->secondaryAddresses, + 'auxiliary' => $this->auxiliary, + ], fn($v) => $v !== null); } - /** - * @inheritDoc - */ - public function capabilities(): array { - return $this->capabilities; - } + public function fromStore(array $data): static + { + $this->serviceTenantId = $data['tid'] ?? null; + $this->serviceIdentifier = $data['sid'] ?? null; + $this->serviceLabel = $data['label'] ?? ''; + $this->serviceEnabled = $data['enabled'] ?? false; - /** - * @inheritDoc - */ - public function in(): string { - return $this->providerId; - } + if (isset($data['primaryAddress']) && is_array($data['primaryAddress'])) { + $this->primaryAddress = $data['primaryAddress']; + } + if (isset($data['secondaryAddresses']) && is_array($data['secondaryAddresses'])) { + $this->secondaryAddresses = $data['secondaryAddresses']; + } + if (isset($data['auxiliary']) && is_array($data['auxiliary'])) { + $this->auxiliary = $data['auxiliary']; + } - /** - * @inheritDoc - */ - public function id(): string|int { - return $this->id; - } - - /** - * @inheritDoc - */ - public function getLabel(): string { - return $this->label; - } - - /** - * Sets the service label - * - * @param string $label - * - * @return self - */ - public function setLabel(string $label): self { - $this->label = $label; return $this; } - /** - * @inheritDoc - */ - public function getScope(): ServiceScope { - return $this->scope; + public function jsonSerialize(): array + { + return array_filter([ + self::PROPERTY_TYPE => self::JSON_TYPE, + self::PROPERTY_PROVIDER => self::PROVIDER_IDENTIFIER, + self::PROPERTY_IDENTIFIER => $this->serviceIdentifier, + self::PROPERTY_LABEL => $this->serviceLabel, + self::PROPERTY_ENABLED => $this->serviceEnabled, + self::PROPERTY_CAPABILITIES => $this->serviceAbilities, + self::PROPERTY_PRIMARY_ADDRESS => $this->primaryAddress, + self::PROPERTY_SECONDARY_ADDRESSES => $this->secondaryAddresses, + self::PROPERTY_AUXILIARY => $this->auxiliary, + ], fn($v) => $v !== null); } - /** - * Sets the service scope - * - * @param ServiceScope $scope - * - * @return self - */ - public function setScope(ServiceScope $scope): self { - $this->scope = $scope; - return $this; - } - - /** - * @inheritDoc - */ - public function getOwner(): ?string { - return $this->owner; - } - - /** - * Sets the service owner - * - * @param string|null $owner - * - * @return self - */ - public function setOwner(?string $owner): self { - $this->owner = $owner; - return $this; - } - - /** - * @inheritDoc - */ - public function getEnabled(): bool { - return $this->enabled; - } - - /** - * Sets the enabled status - * - * @param bool $enabled - * - * @return self - */ - public function setEnabled(bool $enabled): self { - $this->enabled = $enabled; - return $this; - } - - /** - * @inheritDoc - */ - public function getPrimaryAddress(): IAddress { - return $this->primaryAddress ?? new Address(''); - } - - /** - * Sets the primary address - * - * @param IAddress $address - * - * @return self - */ - public function setPrimaryAddress(IAddress $address): self { - $this->primaryAddress = $address; - return $this; - } - - /** - * @inheritDoc - */ - public function getSecondaryAddresses(): array { - return $this->secondaryAddresses; - } - - /** - * Sets the secondary addresses - * - * @param array $addresses - * - * @return self - */ - public function setSecondaryAddresses(array $addresses): self { - $this->secondaryAddresses = $addresses; - return $this; - } - - /** - * Adds a secondary address - * - * @param IAddress $address - * - * @return self - */ - public function addSecondaryAddress(IAddress $address): self { - $this->secondaryAddresses[] = $address; - return $this; - } - - /** - * Gets the service location - * - * @return IServiceLocation|null - */ - public function getLocation(): ?IServiceLocation { - return $this->location; - } - - /** - * Sets the service location - * - * @param IServiceLocation $location - * - * @return self - */ - public function setLocation(IServiceLocation $location): self { - $this->location = $location; - $this->mailer = null; // Reset mailer when location changes - return $this; - } - - /** - * Gets the service identity - * - * @return IServiceIdentity|null - */ - public function getIdentity(): ?IServiceIdentity { - return $this->identity; - } - - /** - * Sets the service identity - * - * @param IServiceIdentity $identity - * - * @return self - */ - public function setIdentity(IServiceIdentity $identity): self { - $this->identity = $identity; - $this->mailer = null; // Reset mailer when identity changes - return $this; - } - - /** - * @inheritDoc - */ - public function handlesAddress(string $address): bool { - $address = strtolower(trim($address)); - - // Check primary address (exact match or catch-all) - if ($this->primaryAddress !== null) { - if ($this->matchesAddress($this->primaryAddress->getAddress(), $address)) { - return true; - } + public function jsonDeserialize(array|string $data, bool $delta = false): static + { + if (is_string($data)) { + $data = json_decode($data, true, 512, JSON_THROW_ON_ERROR); } - - // Check secondary addresses (exact match or catch-all) - foreach ($this->secondaryAddresses as $secondary) { - if ($this->matchesAddress($secondary->getAddress(), $address)) { - return true; - } + + if (isset($data[self::PROPERTY_ENABLED])) { + $this->setEnabled((bool)$data[self::PROPERTY_ENABLED]); } - - return false; - } - - /** - * Check if a pattern matches an address (supports catch-all with *) - * - * @param string $pattern Pattern like "noreply@system" or "*@system" - * @param string $address Address to check - * - * @return bool - */ - private function matchesAddress(string $pattern, string $address): bool { - $pattern = strtolower(trim($pattern)); - $address = strtolower(trim($address)); - - // Exact match - if ($pattern === $address) { - return true; + if (isset($data[self::PROPERTY_LABEL])) { + $this->setLabel($data[self::PROPERTY_LABEL]); } - - // Catch-all pattern (e.g., *@system) - if (str_starts_with($pattern, '*@')) { - $domain = substr($pattern, 2); - return str_ends_with($address, '@' . $domain); + if (isset($data[self::PROPERTY_PRIMARY_ADDRESS])) { + $value = $data[self::PROPERTY_PRIMARY_ADDRESS]; + $this->setPrimaryAddress(is_array($value) ? Address::fromArray($value) : new Address((string)$value)); } - - return false; - } - - /** - * @inheritDoc - */ - public function messageFresh(): IMessageMutable { - $message = new Message(); - - // Pre-populate from address if available - if ($this->primaryAddress !== null) { - $message->setFrom($this->primaryAddress); - } - - return $message; - } - - /** - * @inheritDoc - */ - public function messageSend(IMessageMutable $message): string { - if (!$this->enabled) { - throw SendException::permanent('Service is disabled'); - } - - if ($this->location === null) { - throw SendException::permanent('Service location not configured'); - } - - if (!$message->hasRecipients()) { - throw SendException::permanent('Message has no recipients'); - } - - if (!$message->hasBody()) { - throw SendException::permanent('Message has no body content'); - } - - // Build Symfony Email - $email = $this->buildSymfonyEmail($message); - - // Get or create mailer - $mailer = $this->getMailer(); - - try { - $mailer->send($email); - - // Return message ID (generate one if not available) - return $email->getHeaders()->get('Message-ID')?->getBodyAsString() - ?? $this->generateMessageId(); - - } catch (TransportExceptionInterface $e) { - // Determine if this is a permanent or temporary failure - $message = $e->getMessage(); - $isPermanent = $this->isPermanentFailure($e); - - throw new SendException( - "SMTP delivery failed: $message", - $e->getCode(), - $e, - null, - $isPermanent - ); - } - } - - // Collection operations - SMTP is outbound-only, these are not supported - public function collectionList(?\KTXF\Resource\Filter\IFilter $filter = null, ?\KTXF\Resource\Sort\ISort $sort = null): array { - return []; - } - - public function collectionListFilter(): \KTXF\Resource\Filter\IFilter { - throw new \BadMethodCallException('SMTP service does not support collection operations'); - } - - public function collectionListSort(): \KTXF\Resource\Sort\ISort { - throw new \BadMethodCallException('SMTP service does not support collection operations'); - } - - public function collectionExtant(string|int ...$identifiers): array { - return []; - } - - public function collectionFetch(string|int $identifier): ?\KTXF\Mail\Collection\ICollectionBase { - return null; - } - - // Message operations - SMTP is outbound-only, these are not supported - public function messageList(string|int $collection, ?\KTXF\Resource\Filter\IFilter $filter = null, ?\KTXF\Resource\Sort\ISort $sort = null, ?\KTXF\Resource\Range\IRange $range = null, ?array $properties = null): array { - return []; - } - - public function messageListFilter(): \KTXF\Resource\Filter\IFilter { - throw new \BadMethodCallException('SMTP service does not support message listing'); - } - - public function messageListSort(): \KTXF\Resource\Sort\ISort { - throw new \BadMethodCallException('SMTP service does not support message listing'); - } - - public function messageListRange(\KTXF\Resource\Range\RangeType $type): \KTXF\Resource\Range\IRange { - throw new \BadMethodCallException('SMTP service does not support message listing'); - } - - public function messageDelta(string|int $collection, string $signature, string $detail = 'ids'): array { - return ['signature' => $signature, 'added' => [], 'modified' => [], 'removed' => []]; - } - - public function messageExtant(string|int $collection, string|int ...$identifiers): array { - return []; - } - - public function messageFetch(string|int $collection, string|int ...$identifiers): array { - return []; - } - - public function messageSearch(string $query, ?array $collections = null, ?\KTXF\Resource\Filter\IFilter $filter = null, ?\KTXF\Resource\Sort\ISort $sort = null, ?\KTXF\Resource\Range\IRange $range = null): array { - return []; - } - - /** - * @inheritDoc - */ - public function jsonSerialize(): array { - return [ - self::JSON_PROPERTY_TYPE => self::JSON_TYPE, - self::JSON_PROPERTY_PROVIDER => $this->providerId, - self::JSON_PROPERTY_ID => $this->id, - self::JSON_PROPERTY_LABEL => $this->label, - self::JSON_PROPERTY_SCOPE => $this->scope->value, - self::JSON_PROPERTY_OWNER => $this->owner, - self::JSON_PROPERTY_ENABLED => $this->enabled, - self::JSON_PROPERTY_CAPABILITIES => $this->capabilities, - self::JSON_PROPERTY_PRIMARY_ADDRESS => $this->primaryAddress, - self::JSON_PROPERTY_SECONDARY_ADDRESSES => $this->secondaryAddresses, - ]; - } - - /** - * Build a Symfony Email from IMessageMutable - */ - private function buildSymfonyEmail(IMessageMutable $message): Email { - $email = new Email(); - - // From - $from = $message->getFrom() ?? $this->primaryAddress; - if ($from !== null) { - $email->from(new \Symfony\Component\Mime\Address( - $from->getAddress(), - $from->getName() ?? '' + if (isset($data[self::PROPERTY_SECONDARY_ADDRESSES]) && is_array($data[self::PROPERTY_SECONDARY_ADDRESSES])) { + $this->setSecondaryAddresses(array_map( + fn($addr) => $addr instanceof AddressInterface ? $addr : (is_array($addr) ? Address::fromArray($addr) : new Address((string)$addr)), + $data[self::PROPERTY_SECONDARY_ADDRESSES] )); } - - // Reply-To - $replyTo = $message->getReplyTo(); - if ($replyTo !== null) { - $email->replyTo(new \Symfony\Component\Mime\Address( - $replyTo->getAddress(), - $replyTo->getName() ?? '' - )); + if (isset($data[self::PROPERTY_AUXILIARY]) && is_array($data[self::PROPERTY_AUXILIARY])) { + $this->setAuxiliary($delta ? array_merge($this->auxiliary, $data[self::PROPERTY_AUXILIARY]) : $data[self::PROPERTY_AUXILIARY]); } - - // To - foreach ($message->getTo() as $to) { - $email->addTo(new \Symfony\Component\Mime\Address( - $to->getAddress(), - $to->getName() ?? '' - )); - } - - // CC - foreach ($message->getCc() as $cc) { - $email->addCc(new \Symfony\Component\Mime\Address( - $cc->getAddress(), - $cc->getName() ?? '' - )); - } - - // BCC - foreach ($message->getBcc() as $bcc) { - $email->addBcc(new \Symfony\Component\Mime\Address( - $bcc->getAddress(), - $bcc->getName() ?? '' - )); - } - - // Subject - $email->subject($message->getSubject()); - - // Body - if ($message->getBodyText() !== null) { - $email->text($message->getBodyText()); - } - if ($message->getBodyHtml() !== null) { - $email->html($message->getBodyHtml()); - } - - // Attachments - foreach ($message->getAttachments() as $attachment) { - if ($attachment->isInline()) { - $email->embed( - $attachment->getContent(), - $attachment->getName(), - $attachment->getMimeType() - ); - } else { - $email->attach( - $attachment->getContent(), - $attachment->getName(), - $attachment->getMimeType() - ); - } - } - - // Custom headers - foreach ($message->getHeaders() as $name => $value) { - $email->getHeaders()->addTextHeader($name, $value); - } - - return $email; + + return $this; } - /** - * Get or create the Symfony Mailer instance - */ - private function getMailer(): Mailer { - if ($this->mailer === null) { - $dsn = $this->buildDsn(); - $transport = Transport::fromDsn($dsn); - $this->mailer = new Mailer($transport); - } - - return $this->mailer; + public function capable(string $value): bool + { + return !empty($this->serviceAbilities[$value]); } - /** - * Build the DSN string for Symfony Mailer - */ - private function buildDsn(): string { - if ($this->location === null) { - throw new \RuntimeException('Service location not configured'); - } - - $host = $this->location->getOutboundHost() ?? $this->location->getInboundHost(); - $port = $this->location->getOutboundPort() ?? $this->location->getInboundPort(); - $security = $this->location->getOutboundSecurity() ?? $this->location->getInboundSecurity(); - - if ($host === null) { - throw new \RuntimeException('SMTP host not configured'); - } - - // Determine scheme based on security setting - $scheme = match($security) { - IServiceLocation::SECURITY_SSL => 'smtps', - IServiceLocation::SECURITY_TLS, IServiceLocation::SECURITY_STARTTLS => 'smtp', - default => 'smtp', - }; - - // Build DSN - $dsn = $scheme . '://'; - - // Add credentials if available - if ($this->identity !== null) { - $username = $this->identity->getUsername ?? null; - $password = $this->identity->getPassword ?? null; - - if (method_exists($this->identity, 'getUsername')) { - $username = $this->identity->getUsername(); - } - if (method_exists($this->identity, 'getPassword')) { - $password = $this->identity->getPassword(); - } - - if ($username !== null) { - $dsn .= urlencode($username); - if ($password !== null) { - $dsn .= ':' . urlencode($password); - } - $dsn .= '@'; - } - } - - $dsn .= $host; - - if ($port !== null) { - $dsn .= ':' . $port; - } - - // Disable certificate verification - $dsn .= '?verify_peer=0'; - - return $dsn; + public function capabilities(): array + { + return $this->serviceAbilities; } - /** - * Generate a unique message ID - */ - private function generateMessageId(): string { - $domain = 'ktrix.local'; - if ($this->primaryAddress !== null) { - $parts = explode('@', $this->primaryAddress->getAddress()); - if (count($parts) === 2) { - $domain = $parts[1]; - } - } - - return sprintf('<%s.%s@%s>', - bin2hex(random_bytes(8)), - time(), - $domain + public function provider(): string + { + return self::PROVIDER_IDENTIFIER; + } + + public function identifier(): string|int + { + return $this->serviceIdentifier ?? ''; + } + + public function getLabel(): ?string + { + return $this->serviceLabel; + } + + public function setLabel(string $value): static + { + $this->serviceLabel = $value; + return $this; + } + + public function getEnabled(): bool + { + return $this->serviceEnabled; + } + + public function setEnabled(bool $value): static + { + $this->serviceEnabled = $value; + return $this; + } + + public function getPrimaryAddress(): AddressInterface + { + return Address::fromArray($this->primaryAddress); + } + + public function setPrimaryAddress(AddressInterface $value): static + { + $this->primaryAddress = $value->toArray(); + return $this; + } + + public function getSecondaryAddresses(): array + { + return array_map( + fn($addr) => $addr instanceof AddressInterface ? $addr : Address::fromArray(is_array($addr) ? $addr : ['address' => (string)$addr]), + $this->secondaryAddresses ); } + public function setSecondaryAddresses(array $value): static + { + $this->secondaryAddresses = array_map( + fn($addr) => $addr instanceof AddressInterface ? $addr->toArray() : (is_array($addr) ? $addr : ['address' => (string)$addr]), + $value + ); + return $this; + } + + public function getLocation(): ResourceServiceLocationInterface|null + { + return null; + } + + public function getIdentity(): ResourceServiceIdentityInterface|null + { + return null; + } + + public function getAuxiliary(): array + { + return $this->auxiliary; + } + + public function setAuxiliary(array $value): static + { + $this->auxiliary = $value; + return $this; + } + + // ==================== Route Target Configuration ==================== + + public function getTargetProvider(): string + { + return (string)($this->auxiliary[self::AUX_TARGET_PROVIDER] ?? ''); + } + + public function setTargetProvider(string $value): static + { + $this->auxiliary[self::AUX_TARGET_PROVIDER] = $value; + return $this; + } + + public function getTargetService(): string + { + return (string)($this->auxiliary[self::AUX_TARGET_SERVICE] ?? ''); + } + + public function setTargetService(string $value): static + { + $this->auxiliary[self::AUX_TARGET_SERVICE] = $value; + return $this; + } + + public function getFromAddress(): string + { + return (string)($this->auxiliary[self::AUX_FROM_ADDRESS] ?? ''); + } + + public function setFromAddress(string $value): static + { + $this->auxiliary[self::AUX_FROM_ADDRESS] = $value; + return $this; + } + + public function getFromLabel(): ?string + { + $label = $this->auxiliary[self::AUX_FROM_LABEL] ?? null; + return $label !== null ? (string)$label : null; + } + + public function setFromLabel(?string $value): static + { + $this->auxiliary[self::AUX_FROM_LABEL] = $value; + return $this; + } + + // ==================== Address Matching ==================== + + public function hasAddress(string $address): bool + { + return $this->hasAddressExact($address) || $this->hasAddressPattern($address); + } + /** - * Determine if an exception represents a permanent failure + * Checks whether an address exactly matches the primary or a secondary address */ - private function isPermanentFailure(TransportExceptionInterface $e): bool { - $message = strtolower($e->getMessage()); - - // Common permanent failure indicators - $permanentPatterns = [ - 'user unknown', - 'mailbox not found', - 'invalid recipient', - 'relay access denied', - 'authentication failed', - 'bad recipient', - '550 ', - '551 ', - '552 ', - '553 ', - '554 ', - ]; - - foreach ($permanentPatterns as $pattern) { - if (str_contains($message, $pattern)) { + public function hasAddressExact(string $address): bool + { + $address = strtolower(trim($address)); + if ($address === '') { + return false; + } + + foreach ($this->allAddressPatterns() as $pattern) { + if (!str_starts_with($pattern, '*@') && $pattern === $address) { return true; } } - + return false; } + /** + * Checks whether an address matches a catch-all pattern (e.g. *@system) + */ + public function hasAddressPattern(string $address): bool + { + $address = strtolower(trim($address)); + if ($address === '') { + return false; + } + + foreach ($this->allAddressPatterns() as $pattern) { + if (str_starts_with($pattern, '*@') && str_ends_with($address, substr($pattern, 1))) { + return true; + } + } + + return false; + } + + /** + * @return array lowercase primary + secondary address patterns + */ + private function allAddressPatterns(): array + { + $patterns = []; + if (($this->primaryAddress['address'] ?? '') !== '') { + $patterns[] = strtolower(trim($this->primaryAddress['address'])); + } + foreach ($this->secondaryAddresses as $secondary) { + $value = is_array($secondary) ? ($secondary['address'] ?? '') : (string)$secondary; + if ($value !== '') { + $patterns[] = strtolower(trim($value)); + } + } + return $patterns; + } + + // ==================== Submission (delegated) ==================== + + public function entityFresh(): EntityMutableInterface + { + return $this->resolveTarget()->entityFresh(); + } + + public function entitySubmit(AddressInterface $sender, EntityIdentifierInterface|null $source = null, MessagePropertiesMutableInterface|null $message = null): EntitySubmitResult + { + if (!$this->serviceEnabled) { + return new EntitySubmitResult( + EntitySubmitResult::DISPOSITION_ERROR, + errorCode: 'service_disabled', + errorMessage: 'System mail route is disabled.', + ); + } + + $fromAddress = $this->getFromAddress(); + if ($fromAddress === '') { + return new EntitySubmitResult( + EntitySubmitResult::DISPOSITION_ERROR, + errorCode: 'route_misconfigured', + errorMessage: 'System mail route has no sender address configured.', + ); + } + + try { + $target = $this->resolveTarget(); + } catch (\Throwable $e) { + return new EntitySubmitResult( + EntitySubmitResult::DISPOSITION_ERROR, + errorCode: 'route_unresolved', + errorMessage: $e->getMessage(), + ); + } + + // Rewrite the sender identity: the logical @system address never + // appears on the wire, only the configured real address does. + $from = new Address($fromAddress, $this->getFromLabel()); + $message?->setFrom($from); + + return $target->entitySubmit($from, $source, $message); + } + + /** + * Resolves the backing service this route delivers through + * + * The target service is looked up under the reserved system user, so + * routes can only deliver through tenant/system owned accounts. + * + * @throws RuntimeException when the route target cannot be resolved + */ + private function resolveTarget(): ServiceEntitySubmitInterface + { + if ($this->providerManager === null) { + throw new RuntimeException('System mail route is not bound to a provider manager'); + } + if ($this->serviceTenantId === null) { + throw new RuntimeException('System mail route is not bound to a tenant'); + } + + $providerId = $this->getTargetProvider(); + $serviceId = $this->getTargetService(); + if ($providerId === '' || $serviceId === '') { + throw new RuntimeException('System mail route has no delivery target configured'); + } + if ($providerId === self::PROVIDER_IDENTIFIER) { + throw new RuntimeException('System mail route cannot target the system provider'); + } + + $provider = $this->providerManager->resolve(ProviderInterface::TYPE_MAIL, $providerId); + if ($provider instanceof ProviderBaseInterface === false) { + throw new RuntimeException("Target mail provider '$providerId' is not available"); + } + + $service = $provider->serviceFetch($this->serviceTenantId, ProviderBaseInterface::USER_SYSTEM, $serviceId); + if ($service === null) { + throw new RuntimeException("Target mail service '$providerId/$serviceId' was not found"); + } + if (!$service->getEnabled()) { + throw new RuntimeException("Target mail service '$providerId/$serviceId' is disabled"); + } + if ($service instanceof ServiceEntitySubmitInterface === false) { + throw new RuntimeException("Target mail service '$providerId/$serviceId' does not support submission"); + } + + return $service; + } + + // ==================== Collections / Entities (submit-only) ==================== + + public function collectionList(string|int $location, ?IFilter $filter = null, ?ISort $sort = null): array + { + return []; + } + + public function collectionListFilter(): IFilter + { + throw new BadMethodCallException('System mail route does not support collection operations'); + } + + public function collectionListSort(): ISort + { + throw new BadMethodCallException('System mail route does not support collection operations'); + } + + public function collectionExtant(string|int $location, string|int ...$identifiers): array + { + return array_fill_keys($identifiers, false); + } + + public function collectionFetch(string|int $identifier): null + { + return null; + } + + public function entityListBulk(string|int $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): array + { + return []; + } + + public function entityListStream(string|int $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): Generator + { + yield from []; + } + + public function entityListFilter(): IFilter + { + throw new BadMethodCallException('System mail route does not support entity listing'); + } + + public function entityListSort(): ISort + { + throw new BadMethodCallException('System mail route does not support entity listing'); + } + + public function entityListRange(RangeType $type): IRange + { + throw new BadMethodCallException('System mail route does not support entity listing'); + } + + public function entityDelta(string|int $collection, string $signature): Delta + { + throw new BadMethodCallException('System mail route does not support entity synchronization'); + } + + public function entityExtant(string|int $collection, string|int ...$identifiers): array + { + return array_fill_keys($identifiers, false); + } + + public function entityFetchBulk(EntityIdentifierInterface ...$identifiers): array + { + return []; + } + + public function entityFetchStream(EntityIdentifierInterface ...$identifiers): Generator + { + yield from []; + } + + public function entityDownload(EntityIdentifierInterface $target, array|null $part): BinaryResource + { + throw new BadMethodCallException('System mail route does not support entity download'); + } + } diff --git a/lib/Stores/ServiceStore.php b/lib/Stores/ServiceStore.php index 6df0aaf..d6e9508 100644 --- a/lib/Stores/ServiceStore.php +++ b/lib/Stores/ServiceStore.php @@ -9,354 +9,150 @@ declare(strict_types=1); namespace KTXM\ProviderMailSystem\Stores; -use KTXF\Mail\Entity\Address; -use KTXF\Mail\Service\IServiceBase; -use KTXF\Mail\Service\ServiceIdentityBasic; -use KTXF\Mail\Service\ServiceLocation; -use KTXF\Mail\Service\ServiceScope; -use KTXM\ProviderMailSystem\Providers\Service; use KTXC\Db\DataStore; -use Psr\Log\LoggerInterface; +use KTXF\Utile\UUID; +use KTXM\ProviderMailSystem\Providers\Service; /** - * Service Store - * - * @since 2025.05.01 + * System Mail Route Store + * + * Persists system mail routes (one MongoDB document per route) in the + * collection `provider_system_mail_services`. Routes are tenant scoped — + * they carry no user association and contain no transport credentials, + * only the mapping from a "@system" address to a backing provider/service. + * + * @since 2026.07.01 */ -class ServiceStore { +class ServiceStore +{ + protected const COLLECTION_NAME = 'provider_system_mail_services'; public function __construct( - private DataStore $store, - private LoggerInterface $logger, + protected readonly DataStore $dataStore, ) {} - private string $serviceCollection = 'mail_provider_smtp_service'; + /** + * List routes for a tenant, optionally filtered by route IDs + */ + public function list(string $tenantId, ?array $filter = null): array + { + $filterCondition = [ + 'tid' => $tenantId, + ]; + + if (!empty($filter)) { + $filterCondition['sid'] = ['$in' => array_map('strval', $filter)]; + } + + $cursor = $this->dataStore->selectCollection(self::COLLECTION_NAME)->find($filterCondition); + + $list = []; + foreach ($cursor as $entry) { + $list[$entry['sid']] = $entry; + } + return $list; + } /** - * List all services for a tenant - * - * @param string $tenantId - * - * @return array + * Check existence of routes by IDs for a tenant */ - public function listServices(string $tenantId): array { - try { - $cursor = $this->store->selectCollection($this->serviceCollection)->find([ - 'tid' => $tenantId, - ]); - - $services = []; - foreach ($cursor as $entry) { - $id = (string)($entry['sid'] ?? ''); - if ($id === '') { - continue; - } - - $service = $this->hydrateService($id, is_array($entry) ? $entry : []); - if ($service !== null) { - $services[$id] = $service; - } - } - - return $services; - } catch (\Throwable $e) { - $this->logger->warning('Failed to list services', [ - 'tenantId' => $tenantId, - 'error' => $e->getMessage(), - ]); + public function extant(string $tenantId, array $identifiers): array + { + if (empty($identifiers)) { return []; } + + $cursor = $this->dataStore->selectCollection(self::COLLECTION_NAME)->find( + [ + 'tid' => $tenantId, + 'sid' => ['$in' => array_map('strval', $identifiers)] + ], + ['projection' => ['sid' => 1]] + ); + + $existingIds = []; + foreach ($cursor as $document) { + $existingIds[] = $document['sid']; + } + + $result = []; + foreach ($identifiers as $id) { + $result[(string)$id] = in_array((string)$id, $existingIds, true); + } + + return $result; } /** - * Find a service by address (checks primary, secondary, and catch-all patterns) - * - * @param string $tenantId - * @param string $address Address to search for - * - * @return Service|null + * Retrieve a single route by ID */ - public function findServiceByAddress(string $tenantId, string $address): ?Service { - $address = strtolower(trim($address)); - if ($address === '') { + public function fetch(string $tenantId, string|int $serviceId): ?array + { + $document = $this->dataStore->selectCollection(self::COLLECTION_NAME)->findOne([ + 'tid' => $tenantId, + 'sid' => (string)$serviceId, + ]); + + if (!$document) { return null; } - try { - // Get all services for tenant - $services = $this->listServices($tenantId); - - foreach ($services as $service) { - if ($service->handlesAddress($address)) { - return $service; - } - } - - return null; - } catch (\Throwable $e) { - $this->logger->warning('Failed to find service by address', [ - 'tenantId' => $tenantId, - 'address' => $address, - 'error' => $e->getMessage(), - ]); - return null; - } + return $document; } /** - * Get a specific service - * - * @param string $tenantId - * @param string|int $serviceId - * - * @return Service|null + * Create a new route */ - public function getService(string $tenantId, string|int $serviceId): ?Service { - $serviceId = (string)$serviceId; + public function create(string $tenantId, Service $service): array + { + $document = $service->toStore(); + + $document['tid'] = $tenantId; + $document['sid'] = UUID::v4(); + $document['createdOn'] = new \MongoDB\BSON\UTCDateTime(); + $document['modifiedOn'] = new \MongoDB\BSON\UTCDateTime(); + + $this->dataStore->selectCollection(self::COLLECTION_NAME)->insertOne($document); + + return $document; + } + + /** + * Modify an existing route + */ + public function modify(string $tenantId, Service $service): array + { + $serviceId = (string)$service->identifier(); if ($serviceId === '') { - return null; + throw new \InvalidArgumentException('Service ID is required for update'); } - try { - $entry = $this->store->selectCollection($this->serviceCollection)->findOne([ + $document = $service->toStore(); + $document['modifiedOn'] = new \MongoDB\BSON\UTCDateTime(); + unset($document['sid'], $document['tid'], $document['createdOn']); + + $this->dataStore->selectCollection(self::COLLECTION_NAME)->updateOne( + [ 'tid' => $tenantId, 'sid' => $serviceId, - ]); + ], + ['$set' => $document] + ); - if ($entry === null) { - return null; - } - - return $this->hydrateService($serviceId, is_array($entry) ? $entry : []); - } catch (\Throwable $e) { - $this->logger->warning('Failed to fetch service', [ - 'tenantId' => $tenantId, - 'serviceId' => $serviceId, - 'error' => $e->getMessage(), - ]); - return null; - } + return $document; } /** - * Create a new service - * - * @param string $tenantId - * @param IServiceBase $service - * - * @return string|int Service ID + * Delete a route */ - public function createService(string $tenantId, IServiceBase $service): string|int { - $id = (string)$service->id(); - if ($id === '') { - $id = $this->generateServiceId($tenantId); - } + public function delete(string $tenantId, string|int $serviceId): bool + { + $result = $this->dataStore->selectCollection(self::COLLECTION_NAME)->deleteOne([ + 'tid' => $tenantId, + 'sid' => (string)$serviceId, + ]); - $now = date('c'); - $data = $this->dehydrateService($service); - - try { - $document = array_merge($data, [ - 'tid' => $tenantId, - 'sid' => $id, - 'createdOn' => $now, - 'modifiedOn' => $now, - ]); - - $this->store->selectCollection($this->serviceCollection)->insertOne($document); - return $id; - } catch (\Throwable $e) { - $this->logger->warning('Failed to create service', [ - 'tenantId' => $tenantId, - 'serviceId' => $id, - 'error' => $e->getMessage(), - ]); - throw $e; - } - } - - /** - * Update an existing service - * - * @param string $tenantId - * @param IServiceBase $service - * - * @return string|int Service ID - */ - public function updateService(string $tenantId, IServiceBase $service): string|int { - $id = (string)$service->id(); - if ($id === '') { - $id = $this->generateServiceId($tenantId); - } - - $now = date('c'); - $data = $this->dehydrateService($service); - unset($data['tid'], $data['sid'], $data['createdOn'], $data['modifiedOn']); - - try { - $this->store->selectCollection($this->serviceCollection)->updateOne( - ['tid' => $tenantId, 'sid' => $id], - [ - '$set' => array_merge($data, ['modifiedOn' => $now]), - '$setOnInsert' => ['tid' => $tenantId, 'sid' => $id, 'createdOn' => $now], - ], - ['upsert' => true] - ); - - return $id; - } catch (\Throwable $e) { - $this->logger->warning('Failed to update service', [ - 'tenantId' => $tenantId, - 'serviceId' => $id, - 'error' => $e->getMessage(), - ]); - throw $e; - } - } - - /** - * Delete a service - * - * @param string $tenantId - * @param string|int $serviceId - * - * @return bool - */ - public function deleteService(string $tenantId, string|int $serviceId): bool { - $serviceId = (string)$serviceId; - if ($serviceId === '') { - return false; - } - - try { - $result = $this->store->selectCollection($this->serviceCollection)->deleteOne([ - 'tid' => $tenantId, - 'sid' => $serviceId, - ]); - - return $result->getDeletedCount() === 1; - } catch (\Throwable $e) { - $this->logger->warning('Failed to delete service', [ - 'tenantId' => $tenantId, - 'serviceId' => $serviceId, - 'error' => $e->getMessage(), - ]); - return false; - } - } - - /** - * Generate a unique service ID - */ - private function generateServiceId(string $tenantId): string { - // Try a few times to avoid collisions if a unique index is ever added. - for ($attempt = 0; $attempt < 5; $attempt++) { - $id = sprintf('%08x-%04x', time(), mt_rand(0, 0xffff)); - try { - $existing = $this->store->selectCollection($this->serviceCollection)->findOne([ - 'tid' => $tenantId, - 'sid' => $id, - ], [ - 'projection' => ['sid' => 1, '_id' => 0], - ]); - - if ($existing === null) { - return $id; - } - } catch (\Throwable) { - // If the store is unavailable, fall back to generated id. - return $id; - } - } - - return sprintf('%08x-%04x', time(), mt_rand(0, 0xffff)); - } - - /** - * Hydrate a Service from stored data - */ - private function hydrateService(string|int $id, array $data): ?Service { - try { - $service = new Service( - providerId: 'smtp', - id: $id, - label: $data['label'] ?? '', - scope: ServiceScope::tryFrom($data['scope'] ?? 'system') ?? ServiceScope::System, - owner: $data['owner'] ?? null, - enabled: $data['enabled'] ?? true, - ); - - // Primary address - if (isset($data['primaryAddress'])) { - $service->setPrimaryAddress(Address::fromArray($data['primaryAddress'])); - } - - // Secondary addresses - if (isset($data['secondaryAddresses']) && is_array($data['secondaryAddresses'])) { - foreach ($data['secondaryAddresses'] as $addrData) { - $service->addSecondaryAddress(Address::fromArray($addrData)); - } - } - - // Location - if (isset($data['location'])) { - $service->setLocation(ServiceLocation::fromArray($data['location'])); - } - - // Identity - if (isset($data['identity'])) { - $identityType = $data['identity']['type'] ?? 'basic'; - if ($identityType === 'basic') { - $service->setIdentity(ServiceIdentityBasic::fromArray($data['identity'])); - } - } - - return $service; - - } catch (\Throwable $e) { - $this->logger->warning('Failed to hydrate service', [ - 'id' => $id, - 'error' => $e->getMessage(), - ]); - return null; - } - } - - /** - * Dehydrate a Service to storable data - */ - private function dehydrateService(IServiceBase $service): array { - $data = [ - 'label' => $service->getLabel(), - 'scope' => $service->getScope()->value, - 'owner' => $service->getOwner(), - 'enabled' => $service->getEnabled(), - 'primaryAddress' => $service->getPrimaryAddress()->jsonSerialize(), - 'secondaryAddresses' => array_map( - fn($a) => $a->jsonSerialize(), - $service->getSecondaryAddresses() - ), - ]; - - // Store location if it's a Service instance - if ($service instanceof Service) { - $location = $service->getLocation(); - if ($location !== null) { - $data['location'] = $location->jsonSerialize(); - } - - $identity = $service->getIdentity(); - if ($identity !== null) { - $identityData = $identity->jsonSerialize(); - // Include password for storage (it's excluded from default serialization) - if ($identity instanceof ServiceIdentityBasic) { - $identityData['password'] = $identity->getPassword(); - } - $data['identity'] = $identityData; - } - } - - return $data; + return $result->getDeletedCount() > 0; } } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0f4e1f1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1908 @@ +{ + "name": "provider_mail_system", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "provider_mail_system", + "version": "1.0.0", + "license": "AGPL-3.0-or-later", + "dependencies": { + "pinia": "^3.0.0", + "vue": "^3.5.18", + "vue-router": "^5.0.0", + "vuetify": "^4.0.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.1", + "@vue/tsconfig": "^0.9.0", + "typescript": "~6.0.0", + "vite": "^8.0.0", + "vue-tsc": "^3.0.5" + } + }, + "node_modules/@babel/generator": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz", + "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", + "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/parser": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz", + "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==", + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/types": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", + "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.4" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz", + "integrity": "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue-macros/common": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.3.tgz", + "integrity": "sha512-pphnexn8CDKugcA4TYSKlg1XanBYPbILST+eZK9ZGqG8sVbNR5L0kXEpRqs8+iSznosHt/Jo2k1FGl0tnWIpyg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-sfc": "^3.5.22", + "ast-kit": "^2.1.2", + "local-pkg": "^1.1.2", + "magic-string-ast": "^1.0.2", + "unplugin-utils": "^0.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/vue-macros" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.2.25" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz", + "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.40", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz", + "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz", + "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.40", + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-ssr": "3.5.40", + "@vue/shared": "3.5.40", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz", + "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.10.tgz", + "integrity": "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.10" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz", + "integrity": "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.10", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.10", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz", + "integrity": "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.7.tgz", + "integrity": "sha512-LzmkKinXAMMoh8Jfi/jMUSDUjuPdv8mynH5WJGKfXyZtDw3hQ6GBaoI6Bcnl/Xqlu32q/0Z6i/trp4VXykzyLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.1", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz", + "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz", + "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz", + "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.40", + "@vue/runtime-core": "3.5.40", + "@vue/shared": "3.5.40", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz", + "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz", + "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.9.1.tgz", + "integrity": "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">= 5.8", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ast-kit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", + "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ast-walker-scope": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.9.0.tgz", + "integrity": "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@babel/types": "^7.29.0", + "ast-kit": "^2.2.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/exsolve": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.0.tgz", + "integrity": "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "devOptional": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magic-string-ast": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", + "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.19" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nostics": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/nostics/-/nostics-1.2.0.tgz", + "integrity": "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==", + "license": "MIT" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/unplugin": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.3.0.tgz", + "integrity": "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.4", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@farmfe/core": "*", + "@rspack/core": "*", + "bun-types-no-globals": "*", + "esbuild": "*", + "rolldown": "*", + "rollup": "*", + "unloader": "*", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "@farmfe/core": { + "optional": true + }, + "@rspack/core": { + "optional": true + }, + "bun-types-no-globals": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "unloader": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.2.tgz", + "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/vite": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz", + "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-sfc": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/server-renderer": "3.5.40", + "@vue/shared": "3.5.40" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.2.0.tgz", + "integrity": "sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw==", + "license": "MIT", + "dependencies": { + "@babel/generator": "^8.0.0", + "@vue-macros/common": "^3.1.3", + "@vue/devtools-api": "^8.1.5", + "ast-walker-scope": "^0.9.0", + "chokidar": "^5.0.0", + "json5": "^2.2.3", + "local-pkg": "^1.2.1", + "magic-string": "^0.30.21", + "mlly": "^1.8.2", + "muggle-string": "^0.4.1", + "nostics": "^1.1.4", + "pathe": "^2.0.3", + "picomatch": "^4.0.5", + "scule": "^1.3.0", + "tinyglobby": "^0.2.17", + "unplugin": "^3.3.0", + "unplugin-utils": "^0.3.2", + "yaml": "^2.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@pinia/colada": ">=0.21.2", + "@vue/compiler-sfc": "^3.5.34 || ^4.0.0", + "pinia": "^3.0.4 || ^4.0.2", + "vite": "^7.3.0 || ^8.0.0", + "vue": "^3.5.34 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@pinia/colada": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "pinia": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.1.5.tgz", + "integrity": "sha512-YJipMVAKe5wT5CWf5kTYCaNV7NMNjFVxJkIkJaJ4W/nCxEBzlZzrOsYKeCymdCrFZmBS/+wTWFoUs3Jf/Q6XSQ==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.1.5" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-kit": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.5.tgz", + "integrity": "sha512-FcSAxsi4eWuXLCB7Rv9lj0aIVHHPNVQ2BazGf4RJTc2JCqb4BQg0hk87ZFhminCfl+mD5OUI0rX2cgyu4kJOGA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.1.5", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-shared": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.5.tgz", + "integrity": "sha512-mhT4zcPFhF+Xk1O4BfhhrbXzpmfqY03fS6xGpcllbQG7lDjhQf8pQHcTIhqQIYx1hfwtHmk/6jM96ele0UxPqQ==", + "license": "MIT" + }, + "node_modules/vue-router/node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.7.tgz", + "integrity": "sha512-+C+rgD49wAQ5bUTl2sp5a8Bzg4YoldMNXM+g7CFe604MYcQ8PrZPMQhIjJSzKXtPBCa+C5ayMipqjbA7splekQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.3.7" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/vuetify": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-4.1.5.tgz", + "integrity": "sha512-cR3XMzWbVll9wSdarhH7asT27zm23rMNM5dQ9pMGwTPGwEm0km8mY6ERJAGHIa/nMw0pYXWHjf1O9qJ/0PtZCQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/johnleider" + }, + "peerDependencies": { + "typescript": ">=4.7", + "vite-plugin-vuetify": ">=2.1.0", + "vue": "^3.5.0", + "webpack-plugin-vuetify": ">=3.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vite-plugin-vuetify": { + "optional": true + }, + "webpack-plugin-vuetify": { + "optional": true + } + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0fff0e4 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "provider_mail_system", + "description": "Ktrix System Mail Provider Module", + "version": "1.0.0", + "private": true, + "license": "AGPL-3.0-or-later", + "author": "Ktrix", + "type": "module", + "scripts": { + "build": "vite build --mode production --config vite.config.ts", + "dev": "vite build --mode development --config vite.config.ts", + "watch": "vite build --mode development --watch --config vite.config.ts", + "typecheck": "vue-tsc --noEmit", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" + }, + "dependencies": { + "pinia": "^3.0.0", + "vue": "^3.5.18", + "vue-router": "^5.0.0", + "vuetify": "^4.0.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.1", + "@vue/tsconfig": "^0.9.0", + "typescript": "~6.0.0", + "vite": "^8.0.0", + "vue-tsc": "^3.0.5" + } +} diff --git a/src/components/RuleDialog.vue b/src/components/RuleDialog.vue new file mode 100644 index 0000000..0414396 --- /dev/null +++ b/src/components/RuleDialog.vue @@ -0,0 +1,265 @@ + + + diff --git a/src/integrations.ts b/src/integrations.ts new file mode 100644 index 0000000..4bfc528 --- /dev/null +++ b/src/integrations.ts @@ -0,0 +1,24 @@ +import type { ModuleIntegrations } from '@KTXC/types/moduleTypes' + +const integrations: ModuleIntegrations = { + admin_settings_menu: [ + { + id: 'system_mail', + label: 'System Mail', + path: '/system-mail', + icon: 'mdi-email-lock', + priority: 70, + caption: 'Manage system mail accounts and routing rules', + }, + ], + mail_provider_details: [ + { + id: 'system', + label: 'System Mail', + description: 'Rule-based routing for system-generated mail', + icon: 'mdi-email-lock', + }, + ], +} + +export default integrations diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..6110377 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,13 @@ +import '@/style.css' +import routes from '@/routes' +import integrations from '@/integrations' +import type { App as VueApp } from 'vue' + +export const css = ['__CSS_FILENAME_PLACEHOLDER__'] + +export { routes, integrations } + +export default { + install(_app: VueApp) { + } +} diff --git a/src/pages/Main.vue b/src/pages/Main.vue new file mode 100644 index 0000000..003d2f7 --- /dev/null +++ b/src/pages/Main.vue @@ -0,0 +1,446 @@ + + + diff --git a/src/routes.ts b/src/routes.ts new file mode 100644 index 0000000..bfc0aab --- /dev/null +++ b/src/routes.ts @@ -0,0 +1,14 @@ +const routes = [ + { + name: 'system-mail', + path: '/system-mail', + component: () => import('@/pages/Main.vue'), + meta: { + title: 'System Mail', + requiresAuth: true, + permission: 'mail_manager.system', + } + }, +] + +export default routes diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..c403123 --- /dev/null +++ b/src/style.css @@ -0,0 +1 @@ +/* System Mail Provider module styles */ diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..151aa68 --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..271e3c7 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,18 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "paths": { + "@/*": ["./src/*"], + "@KTXC/*": ["../../core/src/*"], + "@KTXM/MailManager/*": ["../mail_manager/src/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c452f43 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} \ No newline at end of file diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..0720528 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..0ce7484 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,67 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import path from 'path' + +export default defineConfig({ + plugins: [ + vue(), + { + name: 'inject-css-filename', + enforce: 'post', + generateBundle(_options, bundle) { + const cssFile = Object.keys(bundle).find(name => name.endsWith('.css')) + if (!cssFile) { + return + } + + for (const fileName of Object.keys(bundle)) { + const chunk = bundle[fileName] + if (chunk.type === 'chunk' && chunk.code.includes('__CSS_FILENAME_PLACEHOLDER__')) { + chunk.code = chunk.code.replace(/__CSS_FILENAME_PLACEHOLDER__/g, `static/${cssFile}`) + } + } + } + } + ], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + '@KTXC': path.resolve(__dirname, '../../core/src'), + '@KTXM/MailManager': path.resolve(__dirname, '../mail_manager/src'), + }, + }, + build: { + outDir: 'static', + emptyOutDir: true, + sourcemap: true, + lib: { + entry: path.resolve(__dirname, 'src/main.ts'), + formats: ['es'], + fileName: () => 'module.mjs', + }, + rollupOptions: { + external: [ + 'vue', + 'vue-router', + 'pinia', + /^@KTXM\/MailManager\//, + ], + output: { + paths: (id) => { + if (id.startsWith('@KTXM/MailManager/')) { + return '/modules/mail_manager/static/module.mjs' + } + + return id + }, + assetFileNames: assetInfo => { + if (assetInfo.name?.endsWith('.css')) { + return 'provider_mail_system-[hash].css' + } + + return '[name]-[hash][extname]' + } + } + }, + }, +})