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 573 additions and 833 deletions

View File

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

View File

@@ -57,15 +57,15 @@ return [
'per_tenant' => false, 'per_tenant' => false,
// ── file driver options ──────────────────────────────────────────────── // ── 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, 'path' => null,
// Log channel — used as the filename without extension. // 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', 'channel' => 'app',
// ── syslog driver options ────────────────────────────────────────────── // ── 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', 'ident' => 'ktrix',
// openlog() facility constant. Common values: LOG_USER, LOG_LOCAL0 … LOG_LOCAL7 // openlog() facility constant. Common values: LOG_USER, LOG_LOCAL0 … LOG_LOCAL7

View File

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

View File

@@ -20,10 +20,8 @@ class PlainFileLogger implements LoggerInterface
*/ */
public function __construct(string $logDir, string $channel = 'app') public function __construct(string $logDir, string $channel = 'app')
{ {
if (!is_dir($logDir)) {
@mkdir($logDir, 0775, true);
}
$this->logFile = rtrim($logDir, '/') . '/' . $channel . '.log'; $this->logFile = rtrim($logDir, '/') . '/' . $channel . '.log';
$this->ensureWritablePath();
} }
public function emergency($message, array $context = []): void { $this->log('emergency', $message, $context); } 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 public function log($level, $message, array $context = []): void
{ {
$this->ensureWritablePath();
$dt = \DateTimeImmutable::createFromFormat('U.u', sprintf('%.6F', microtime(true))); $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'); $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; $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 private function interpolate(string $message, array $context): string

1296
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -68,11 +68,10 @@ interface ServiceCollectionMutableInterface extends ServiceBaseInterface {
* *
* @param string|int $identifier Collection ID * @param string|int $identifier Collection ID
* @param bool $force Force deletion even if not empty * @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 * Moves a collection to a new parent