11 Commits

Author SHA1 Message Date
8a48c3f17f chore(deps): update dependency vite to v8
All checks were successful
Build Test / build (pull_request) Successful in 20s
JS Unit Tests / test (pull_request) Successful in 21s
PHP Unit Tests / test (pull_request) Successful in 47s
2026-05-06 16:34:03 +00:00
0d5ed4de1e Merge pull request 'chore(deps): update dependency vite-plugin-static-copy to v4' (#48) from renovate/vite-plugin-static-copy-4.x into main
Reviewed-on: #48
2026-05-06 16:30:16 +00:00
5af979cefe Merge pull request 'fix(deps): update dependency symfony/console to v8' (#49) from renovate/major-symfony into main
Reviewed-on: #49
2026-05-06 16:30:05 +00:00
fe2a8fbfa3 Merge pull request 'fix(deps): update dependency vuetify to v4' (#50) from renovate/vuetify-4.x into main
Reviewed-on: #50
2026-05-06 16:29:49 +00:00
ea09a821ab Merge pull request 'refactor: mail collections delete' (#52) from refactor/mail-collections into main
Reviewed-on: #52
2026-05-06 16:28:44 +00:00
44584fc306 refactor: mail collections delete
All checks were successful
JS Unit Tests / test (pull_request) Successful in 14s
Build Test / build (pull_request) Successful in 17s
PHP Unit Tests / test (pull_request) Successful in 42s
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
2026-05-06 12:28:18 -04:00
452048de64 Merge pull request 'fix: plain logging' (#51) from fix/plain-logging into main
Reviewed-on: #51
2026-05-06 16:25:43 +00:00
a7a94f93d8 fix: plain logging
All checks were successful
Build Test / build (pull_request) Successful in 16s
JS Unit Tests / test (pull_request) Successful in 15s
PHP Unit Tests / test (pull_request) Successful in 44s
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
2026-05-06 12:24:53 -04:00
2b89e50b77 fix(deps): update dependency vuetify to v4
All checks were successful
Build Test / build (pull_request) Successful in 19s
JS Unit Tests / test (pull_request) Successful in 19s
PHP Unit Tests / test (pull_request) Successful in 52s
2026-04-25 20:06:42 +00:00
4d758df68e fix(deps): update dependency symfony/console to v8
All checks were successful
JS Unit Tests / test (pull_request) Successful in 18s
Build Test / build (pull_request) Successful in 24s
PHP Unit Tests / test (pull_request) Successful in 51s
2026-04-25 20:06:37 +00:00
2cd85c3ffd chore(deps): update dependency vite-plugin-static-copy to v4
All checks were successful
JS Unit Tests / test (pull_request) Successful in 23s
Build Test / build (pull_request) Successful in 27s
PHP Unit Tests / test (pull_request) Successful in 1m33s
2026-04-25 20:06:21 +00:00
10 changed files with 75 additions and 59 deletions

View File

@@ -11,7 +11,7 @@
"mongodb/mongodb": "^2.1",
"php-di/php-di": "*",
"phpseclib/phpseclib": "^3.0",
"symfony/console": "^7.0"
"symfony/console": "^8.0"
},
"require-dev": {
"phpunit/phpunit": "^11.0"

54
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b14b010c422e222aeb1e33104e2a09ec",
"content-hash": "f77626277cdeb9207c6aab5d2d9a19b2",
"packages": [
{
"name": "laravel/serializable-closure",
@@ -606,47 +606,39 @@
},
{
"name": "symfony/console",
"version": "v7.4.8",
"version": "v8.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707"
"reference": "5b66d385dc58f69652e56f78a4184615e3f2b7f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707",
"reference": "1e92e39c51f95b88e3d66fa2d9f06d1fb45dd707",
"url": "https://api.github.com/repos/symfony/console/zipball/5b66d385dc58f69652e56f78a4184615e3f2b7f7",
"reference": "5b66d385dc58f69652e56f78a4184615e3f2b7f7",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0",
"php": ">=8.4",
"symfony/polyfill-mbstring": "^1.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/string": "^7.2|^8.0"
},
"conflict": {
"symfony/dependency-injection": "<6.4",
"symfony/dotenv": "<6.4",
"symfony/event-dispatcher": "<6.4",
"symfony/lock": "<6.4",
"symfony/process": "<6.4"
"symfony/string": "^7.4|^8.0"
},
"provide": {
"psr/log-implementation": "1.0|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/event-dispatcher": "^6.4|^7.0|^8.0",
"symfony/http-foundation": "^6.4|^7.0|^8.0",
"symfony/http-kernel": "^6.4|^7.0|^8.0",
"symfony/lock": "^6.4|^7.0|^8.0",
"symfony/messenger": "^6.4|^7.0|^8.0",
"symfony/process": "^6.4|^7.0|^8.0",
"symfony/stopwatch": "^6.4|^7.0|^8.0",
"symfony/var-dumper": "^6.4|^7.0|^8.0"
"symfony/config": "^7.4|^8.0",
"symfony/dependency-injection": "^7.4|^8.0",
"symfony/event-dispatcher": "^7.4|^8.0",
"symfony/http-foundation": "^7.4|^8.0",
"symfony/http-kernel": "^7.4|^8.0",
"symfony/lock": "^7.4|^8.0",
"symfony/messenger": "^7.4|^8.0",
"symfony/process": "^7.4|^8.0",
"symfony/stopwatch": "^7.4|^8.0",
"symfony/var-dumper": "^7.4|^8.0"
},
"type": "library",
"autoload": {
@@ -680,7 +672,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.4.8"
"source": "https://github.com/symfony/console/tree/v8.0.8"
},
"funding": [
{
@@ -700,7 +692,7 @@
"type": "tidelift"
}
],
"time": "2026-03-30T13:54:39+00:00"
"time": "2026-03-30T15:14:47+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -3148,7 +3140,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
@@ -3156,6 +3148,6 @@
"ext-ctype": "*",
"ext-iconv": "*"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
"platform-dev": {},
"plugin-api-version": "2.9.0"
}

View File

@@ -57,15 +57,15 @@ return [
'per_tenant' => false,
// ── file driver options ────────────────────────────────────────────────
// Absolute path to the log directory. null = <project_root>/var/log
// Absolute path to the log directory. null = <project_root>/var/logs
'path' => null,
// Log channel — used as the filename without extension.
// 'app' → var/log/app.jsonl (or var/log/tenant/{id}/app.jsonl when per_tenant = true)
// 'app' → var/logs/app.jsonl (or var/logs/tenant/{id}/app.jsonl when per_tenant = true)
'channel' => 'app',
// ── syslog driver options ──────────────────────────────────────────────
// Identity tag passed to openlog(); visible in /var/log/syslog and journalctl -t ktrix
// Identity tag passed to openlog(); visible in /var/logs/syslog and journalctl -t ktrix
'ident' => 'ktrix',
// openlog() facility constant. Common values: LOG_USER, LOG_LOCAL0 … LOG_LOCAL7

View File

@@ -79,7 +79,7 @@ class LoggerFactory
$path = $logConfig['path'] ?? null;
if ($path === null || $path === '') {
$path = $projectDir . '/var/log';
$path = $projectDir . '/var/logs';
}
return new FileLogger($path, $channel);

View File

@@ -20,10 +20,8 @@ class PlainFileLogger implements LoggerInterface
*/
public function __construct(string $logDir, string $channel = 'app')
{
if (!is_dir($logDir)) {
@mkdir($logDir, 0775, true);
}
$this->logFile = rtrim($logDir, '/') . '/' . $channel . '.log';
$this->ensureWritablePath();
}
public function emergency($message, array $context = []): void { $this->log('emergency', $message, $context); }
@@ -37,12 +35,38 @@ class PlainFileLogger implements LoggerInterface
public function log($level, $message, array $context = []): void
{
$this->ensureWritablePath();
$dt = \DateTimeImmutable::createFromFormat('U.u', sprintf('%.6F', microtime(true)));
$timestamp = $dt?->format('Y-m-d H:i:s.u') ?? date('Y-m-d H:i:s');
$line = $timestamp . ' ' . $this->interpolate((string) $message, $context) . PHP_EOL;
@file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
if (@file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX) === false) {
error_log(sprintf('Failed to write to log file: %s', $this->logFile));
}
}
private function ensureWritablePath(): void
{
$logDir = dirname($this->logFile);
if (!is_dir($logDir)) {
@mkdir($logDir, 0777, true);
}
if (is_dir($logDir)) {
@chmod($logDir, 0777);
}
if (!file_exists($this->logFile)) {
@touch($this->logFile);
}
clearstatcache(true, $this->logFile);
if (file_exists($this->logFile)) {
@chmod($this->logFile, 0666);
}
}
private function interpolate(string $message, array $context): string

26
package-lock.json generated
View File

@@ -24,7 +24,7 @@
"vue": "3.5.28",
"vue-router": "5.0.2",
"vue3-perfect-scrollbar": "2.0.0",
"vuetify": "3.11.8"
"vuetify": "4.0.6"
},
"devDependencies": {
"@eslint/js": "^9.18.0",
@@ -45,7 +45,7 @@
"typescript": "5.9.3",
"typescript-eslint": "^8.59.0",
"vite": "8.0.10",
"vite-plugin-static-copy": "^3.4.0",
"vite-plugin-static-copy": "^4.0.0",
"vitest": "^4.0.18",
"vue-cli-plugin-vuetify": "2.5.8",
"vue-tsc": "^3.2.7"
@@ -1372,9 +1372,9 @@
"license": "MIT"
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -6394,9 +6394,9 @@
}
},
"node_modules/vite-plugin-static-copy": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.4.0.tgz",
"integrity": "sha512-ekryzCw0ouAOE8tw4RvVL/dfqguXzumsV3FBKoKso4MQ1MUUrUXtl5RI4KpJQUNGqFEsg9kxl4EvDl02YtA9VQ==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-4.1.0.tgz",
"integrity": "sha512-9XOarNV7LgP0KBB7AApxdgFikLXx3daZdqjC3AevYsL6MrUH62zphonLUs2a6LZc1HN1GY+vQdheZ8VVJb6dQQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6406,14 +6406,14 @@
"tinyglobby": "^0.2.15"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
"node": "^22.0.0 || >=24.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/sapphi-red"
},
"peerDependencies": {
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/vite-plugin-static-copy/node_modules/chokidar": {
@@ -6863,9 +6863,9 @@
}
},
"node_modules/vuetify": {
"version": "3.11.8",
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.11.8.tgz",
"integrity": "sha512-4iKnntOnLFFklygZjzlVfcHrtLO8+iK4HOhiia6HP2U8v82x+ngaSCgm+epvPrGyCMfCpfuEttqD2qElrr1axw==",
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-4.0.6.tgz",
"integrity": "sha512-a4kasYa3SuFI5IOG/0Fx04M4TWeef5mCdTTKFlaxt86oz16RMQ/DJ3BynlOnIdUODt7NxYIkdLYud070/vVEUQ==",
"license": "MIT",
"funding": {
"type": "github",

View File

@@ -36,7 +36,7 @@
"vue": "3.5.28",
"vue-router": "5.0.2",
"vue3-perfect-scrollbar": "2.0.0",
"vuetify": "3.11.8"
"vuetify": "4.0.6"
},
"devDependencies": {
"@eslint/js": "^9.18.0",
@@ -57,7 +57,7 @@
"typescript": "5.9.3",
"typescript-eslint": "^8.59.0",
"vite": "8.0.10",
"vite-plugin-static-copy": "^3.4.0",
"vite-plugin-static-copy": "^4.0.0",
"vitest": "^4.0.18",
"vue-cli-plugin-vuetify": "2.5.8",
"vue-tsc": "^3.2.7"

View File

@@ -20,6 +20,7 @@ use JsonSerializable;
*/
enum CollectionRoles: string implements JsonSerializable {
case None = '';
case Inbox = 'inbox';
case Drafts = 'drafts';
case Sent = 'sent';
@@ -28,7 +29,6 @@ enum CollectionRoles: string implements JsonSerializable {
case Archive = 'archive';
case Outbox = 'outbox';
case Queue = 'queue';
case Custom = 'custom';
public function jsonSerialize(): string {
return $this->value;

View File

@@ -38,6 +38,7 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface {
// Collection Filter
public const CAPABILITY_COLLECTION_FILTER_LABEL = 'label';
public const CAPABILITY_COLLECTION_FILTER_ROLE = 'role';
public const CAPABILITY_COLLECTION_FILTER_SUBSCRIBED = 'subscribed';
// Collection Sort
public const CAPABILITY_COLLECTION_SORT_LABEL = 'label';
public const CAPABILITY_COLLECTION_SORT_RANK = 'rank';

View File

@@ -68,11 +68,10 @@ interface ServiceCollectionMutableInterface extends ServiceBaseInterface {
*
* @param string|int $identifier Collection ID
* @param bool $force Force deletion even if not empty
* @param bool $recursive Recursively delete contents
*
* @return bool True if deleted
* @return CollectionBaseInterface|true Collection object on soft delete, true on hard delete
*/
public function collectionDelete(string|int $identifier, bool $force = false, bool $recursive = false): bool;
public function collectionDelete(string|int $identifier, bool $force = false): CollectionBaseInterface | true;
/**
* Moves a collection to a new parent