2 Commits

Author SHA1 Message Date
Sebastian 37491eb5c3 chore(deps): update vitest monorepo to v4.1.10
PHP Unit Tests / test (pull_request) Failing after 11m41s
JS Unit Tests / test (pull_request) Failing after 12m2s
Build Test / test (pull_request) Failing after 12m4s
2026-07-20 03:02:44 +00:00
Sebastian c736e3ebf9 feat: create system user accounts
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
2026-07-19 06:55:01 -04:00
8 changed files with 167 additions and 107 deletions
+19
View File
@@ -23,6 +23,7 @@ use KTXF\Resource\Identifier\EntityIdentifier;
use KTXF\Resource\Identifier\ResourceIdentifier; use KTXF\Resource\Identifier\ResourceIdentifier;
use KTXF\Resource\Identifier\ResourceIdentifiers; use KTXF\Resource\Identifier\ResourceIdentifiers;
use KTXF\Resource\Identifier\ServiceIdentifier; use KTXF\Resource\Identifier\ServiceIdentifier;
use KTXF\Mail\Provider\ProviderBaseInterface;
use KTXF\Resource\Provider\ResourceServiceLocationInterface; use KTXF\Resource\Provider\ResourceServiceLocationInterface;
use KTXF\Routing\Attributes\AuthenticatedRoute; use KTXF\Routing\Attributes\AuthenticatedRoute;
use KTXM\MailManager\Manager; use KTXM\MailManager\Manager;
@@ -82,6 +83,24 @@ class DefaultController extends ControllerAbstract {
$tenantId = $this->tenantIdentity->identifier(); $tenantId = $this->tenantIdentity->identifier();
$userId = $this->userIdentity->identifier(); $userId = $this->userIdentity->identifier();
// acting-user override: only the reserved system context is permitted,
// gated on the system mail management permission
if ($user !== null && $user !== $userId) {
if ($user !== ProviderBaseInterface::USER_SYSTEM || !$this->userIdentity->hasPermission('mail_manager.system')) {
return new JsonResponse([
'version' => $version,
'transaction' => $transaction,
'operation' => $operation,
'status' => 'error',
'data' => [
'code' => JsonResponse::HTTP_FORBIDDEN,
'message' => 'Not permitted to act as user: ' . $user
]
], JsonResponse::HTTP_FORBIDDEN);
}
$userId = $user;
}
try { try {
if ($operation !== null) { if ($operation !== null) {
+14 -6
View File
@@ -236,7 +236,14 @@ class Manager {
$serviceId = $provider->serviceCreate($tenantId, $userId, $service); $serviceId = $provider->serviceCreate($tenantId, $userId, $service);
// Fetch and return the created service // Fetch and return the created service
return $provider->serviceFetch($tenantId, $userId, $serviceId); $createdService = $provider->serviceFetch($tenantId, $userId, $serviceId);
if ($createdService === null) {
throw new \RuntimeException(
"Provider '$providerId' created service '$serviceId', but it could not be fetched"
);
}
return $createdService;
} }
/** /**
@@ -1264,9 +1271,13 @@ class Manager {
} }
public function entitySubmit(string $tenantId, string $userId, AddressInterface|string $sender, EntityIdentifierInterface|null $source = null, MessagePropertiesMutableInterface|array|null $message = null): EntitySubmitResult { public function entitySubmit(string $tenantId, string $userId, AddressInterface|string $sender, EntityIdentifierInterface|null $source = null, MessagePropertiesMutableInterface|array|null $message = null): EntitySubmitResult {
$service = $this->serviceFindByAddress($tenantId, $userId, $sender); if ($sender instanceof AddressInterface === false) {
$sender = new Address($sender);
}
$service = $this->serviceFindByAddress($tenantId, $userId, $sender->getAddress());
if ($service === null || $service->getEnabled() === false) { if ($service === null || $service->getEnabled() === false) {
throw new InvalidArgumentException("Service not found for sender '{$sender}' or service is disabled"); throw new InvalidArgumentException("Service not found for sender '{$sender->getAddress()}' or service is disabled");
} }
if ($service instanceof ServiceEntitySubmitInterface === false) { if ($service instanceof ServiceEntitySubmitInterface === false) {
throw new InvalidArgumentException("Service '{$service->identifier()}' does not support entity submission"); throw new InvalidArgumentException("Service '{$service->identifier()}' does not support entity submission");
@@ -1276,9 +1287,6 @@ class Manager {
throw new InvalidArgumentException("At least one of source or message must be provided for entity submission"); throw new InvalidArgumentException("At least one of source or message must be provided for entity submission");
} }
if ($sender instanceof AddressInterface === false) {
$sender = new Address($sender);
}
if ($message !== null && $message instanceof MessagePropertiesMutableInterface === false) { if ($message !== null && $message instanceof MessagePropertiesMutableInterface === false) {
$message = $service->entityFresh()->getProperties()->jsonDeserialize($message); $message = $service->entityFresh()->getProperties()->jsonDeserialize($message);
} }
+5
View File
@@ -50,6 +50,11 @@ class Module extends ModuleInstanceAbstract implements ModuleBrowserInterface
'description' => 'View and access the mail manager module', 'description' => 'View and access the mail manager module',
'group' => 'Mail Management' 'group' => 'Mail Management'
], ],
'mail_manager.system' => [
'label' => 'Manage System Mail',
'description' => 'Manage system mail accounts and routing rules (act in the reserved system user context)',
'group' => 'Mail Management'
],
]; ];
} }
+55 -55
View File
@@ -572,14 +572,14 @@
} }
}, },
"node_modules/@vitest/coverage-v8": { "node_modules/@vitest/coverage-v8": {
"version": "4.1.9", "version": "4.1.10",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.9.tgz", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz",
"integrity": "sha512-G9/lgqibheLVBDRuya45EbsEXTYcWoSG+TLg7i2axuzx0Eq62eXn+aWXyaVdV5vKvFSWd6ywcX8hA7la9Pvu8g==", "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@bcoe/v8-coverage": "^1.0.2", "@bcoe/v8-coverage": "^1.0.2",
"@vitest/utils": "4.1.9", "@vitest/utils": "4.1.10",
"ast-v8-to-istanbul": "^1.0.0", "ast-v8-to-istanbul": "^1.0.0",
"istanbul-lib-coverage": "^3.2.2", "istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1", "istanbul-lib-report": "^3.0.1",
@@ -593,8 +593,8 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
}, },
"peerDependencies": { "peerDependencies": {
"@vitest/browser": "4.1.9", "@vitest/browser": "4.1.10",
"vitest": "4.1.9" "vitest": "4.1.10"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@vitest/browser": { "@vitest/browser": {
@@ -603,17 +603,17 @@
} }
}, },
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
"version": "4.1.9", "version": "4.1.10",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz",
"integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@standard-schema/spec": "^1.1.0", "@standard-schema/spec": "^1.1.0",
"@types/chai": "^5.2.2", "@types/chai": "^5.2.2",
"@vitest/spy": "4.1.9", "@vitest/spy": "4.1.10",
"@vitest/utils": "4.1.9", "@vitest/utils": "4.1.10",
"chai": "^6.2.2", "chai": "^6.2.2",
"tinyrainbow": "^3.1.0" "tinyrainbow": "^3.1.0"
}, },
@@ -622,14 +622,14 @@
} }
}, },
"node_modules/@vitest/mocker": { "node_modules/@vitest/mocker": {
"version": "4.1.9", "version": "4.1.10",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz",
"integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@vitest/spy": "4.1.9", "@vitest/spy": "4.1.10",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
"magic-string": "^0.30.21" "magic-string": "^0.30.21"
}, },
@@ -661,9 +661,9 @@
} }
}, },
"node_modules/@vitest/pretty-format": { "node_modules/@vitest/pretty-format": {
"version": "4.1.9", "version": "4.1.10",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz",
"integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -674,14 +674,14 @@
} }
}, },
"node_modules/@vitest/runner": { "node_modules/@vitest/runner": {
"version": "4.1.9", "version": "4.1.10",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz",
"integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@vitest/utils": "4.1.9", "@vitest/utils": "4.1.10",
"pathe": "^2.0.3" "pathe": "^2.0.3"
}, },
"funding": { "funding": {
@@ -689,15 +689,15 @@
} }
}, },
"node_modules/@vitest/snapshot": { "node_modules/@vitest/snapshot": {
"version": "4.1.9", "version": "4.1.10",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz",
"integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@vitest/pretty-format": "4.1.9", "@vitest/pretty-format": "4.1.10",
"@vitest/utils": "4.1.9", "@vitest/utils": "4.1.10",
"magic-string": "^0.30.21", "magic-string": "^0.30.21",
"pathe": "^2.0.3" "pathe": "^2.0.3"
}, },
@@ -706,9 +706,9 @@
} }
}, },
"node_modules/@vitest/spy": { "node_modules/@vitest/spy": {
"version": "4.1.9", "version": "4.1.10",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz",
"integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
@@ -717,13 +717,13 @@
} }
}, },
"node_modules/@vitest/ui": { "node_modules/@vitest/ui": {
"version": "4.1.9", "version": "4.1.10",
"resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.9.tgz", "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.10.tgz",
"integrity": "sha512-U/cRvtqfEPj27FI1n9cyUvi4vXXdcLhjJiI+InYKdk8hP4VrS6RXOjGL7rfFaeBc37iRKANsR6eEzIoC7lmgBQ==", "integrity": "sha512-EOUqfXHTXtpSHsyLHH40ts3Ue+hRhSGwzwzMlK0dTEOLSDYyOXLyr5JDGmHQWhN2DYI30gw6dVx3cdgM9FZl+Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/utils": "4.1.9", "@vitest/utils": "4.1.10",
"fflate": "^0.8.2", "fflate": "^0.8.2",
"flatted": "^3.4.2", "flatted": "^3.4.2",
"pathe": "^2.0.3", "pathe": "^2.0.3",
@@ -735,17 +735,17 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
}, },
"peerDependencies": { "peerDependencies": {
"vitest": "4.1.9" "vitest": "4.1.10"
} }
}, },
"node_modules/@vitest/utils": { "node_modules/@vitest/utils": {
"version": "4.1.9", "version": "4.1.10",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz",
"integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "4.1.9", "@vitest/pretty-format": "4.1.10",
"convert-source-map": "^2.0.0", "convert-source-map": "^2.0.0",
"tinyrainbow": "^3.1.0" "tinyrainbow": "^3.1.0"
}, },
@@ -2223,20 +2223,20 @@
} }
}, },
"node_modules/vitest": { "node_modules/vitest": {
"version": "4.1.9", "version": "4.1.10",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz",
"integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"@vitest/expect": "4.1.9", "@vitest/expect": "4.1.10",
"@vitest/mocker": "4.1.9", "@vitest/mocker": "4.1.10",
"@vitest/pretty-format": "4.1.9", "@vitest/pretty-format": "4.1.10",
"@vitest/runner": "4.1.9", "@vitest/runner": "4.1.10",
"@vitest/snapshot": "4.1.9", "@vitest/snapshot": "4.1.10",
"@vitest/spy": "4.1.9", "@vitest/spy": "4.1.10",
"@vitest/utils": "4.1.9", "@vitest/utils": "4.1.10",
"es-module-lexer": "^2.0.0", "es-module-lexer": "^2.0.0",
"expect-type": "^1.3.0", "expect-type": "^1.3.0",
"magic-string": "^0.30.21", "magic-string": "^0.30.21",
@@ -2264,12 +2264,12 @@
"@edge-runtime/vm": "*", "@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0", "@opentelemetry/api": "^1.9.0",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
"@vitest/browser-playwright": "4.1.9", "@vitest/browser-playwright": "4.1.10",
"@vitest/browser-preview": "4.1.9", "@vitest/browser-preview": "4.1.10",
"@vitest/browser-webdriverio": "4.1.9", "@vitest/browser-webdriverio": "4.1.10",
"@vitest/coverage-istanbul": "4.1.9", "@vitest/coverage-istanbul": "4.1.10",
"@vitest/coverage-v8": "4.1.9", "@vitest/coverage-v8": "4.1.10",
"@vitest/ui": "4.1.9", "@vitest/ui": "4.1.10",
"happy-dom": "*", "happy-dom": "*",
"jsdom": "*", "jsdom": "*",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0" "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+7 -3
View File
@@ -32,6 +32,7 @@ const MANUAL_STEPS = {
const props = defineProps<{ const props = defineProps<{
modelValue: boolean modelValue: boolean
user?: string
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
@@ -242,7 +243,8 @@ async function handleDiscover() {
discoverSecret.value || undefined, discoverSecret.value || undefined,
discoverHostname.value || undefined, discoverHostname.value || undefined,
identifier, identifier,
(service) => { discoveredService = service } (service) => { discoveredService = service },
props.user
) )
// Success - check if we got results for this provider // Success - check if we got results for this provider
@@ -384,7 +386,8 @@ async function testConnection() {
selectedProvider.value.identifier, selectedProvider.value.identifier,
null, null,
selectedService.value.location, selectedService.value.location,
selectedService.value.identity selectedService.value.identity,
props.user
) )
return testResult return testResult
@@ -410,7 +413,8 @@ async function saveAccount() {
await servicesStore.create( await servicesStore.create(
selectedProvider.value.identifier, selectedProvider.value.identifier,
accountData accountData,
props.user
) )
emit('saved') emit('saved')
+13 -4
View File
@@ -14,6 +14,7 @@ const props = defineProps<{
modelValue: boolean modelValue: boolean
serviceProvider: string serviceProvider: string
serviceIdentifier: string | number serviceIdentifier: string | number
user?: string
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
@@ -103,7 +104,10 @@ async function load() {
try { try {
const [provider, service] = await Promise.all([ const [provider, service] = await Promise.all([
providersStore.provider(props.serviceProvider) ?? providersStore.fetch(props.serviceProvider), providersStore.provider(props.serviceProvider) ?? providersStore.fetch(props.serviceProvider),
servicesStore.service(props.serviceProvider, props.serviceIdentifier) ?? servicesStore.fetch(props.serviceProvider, props.serviceIdentifier) // acting-user context always fetches fresh, bypassing the shared cache
props.user
? servicesStore.fetch(props.serviceProvider, props.serviceIdentifier, props.user)
: servicesStore.service(props.serviceProvider, props.serviceIdentifier) ?? servicesStore.fetch(props.serviceProvider, props.serviceIdentifier)
]) ])
localProvider.value = provider.clone() localProvider.value = provider.clone()
@@ -161,12 +165,16 @@ async function testConnection() {
localService.value.provider, localService.value.provider,
null, null,
localService.value.location, localService.value.location,
localService.value.identity localService.value.identity,
props.user
) )
} else { } else {
testResult = await servicesStore.test( testResult = await servicesStore.test(
localService.value.provider, localService.value.provider,
localService.value.identifier localService.value.identifier,
undefined,
undefined,
props.user
) )
} }
@@ -199,7 +207,8 @@ async function saveAccount() {
localService.value.provider, localService.value.provider,
localService.value.identifier as string | number, localService.value.identifier as string | number,
true, // delta update true, // delta update
localService.value localService.value,
props.user
) )
emit('saved') emit('saved')
+18 -16
View File
@@ -47,8 +47,8 @@ export const serviceService = {
* *
* @returns Promise with service object list grouped by provider and keyed by service identifier * @returns Promise with service object list grouped by provider and keyed by service identifier
*/ */
async list(request: ServiceListRequest = {}): Promise<Record<string, Record<string, ServiceObject>>> { async list(request: ServiceListRequest = {}, user?: string): Promise<Record<string, Record<string, ServiceObject>>> {
const response = await transceivePost<ServiceListRequest, ServiceListResponse>('service.list', request); const response = await transceivePost<ServiceListRequest, ServiceListResponse>('service.list', request, user);
// Convert nested response to ServiceObject instances // Convert nested response to ServiceObject instances
const providerList: Record<string, Record<string, ServiceObject>> = {}; const providerList: Record<string, Record<string, ServiceObject>> = {};
@@ -70,8 +70,8 @@ export const serviceService = {
* *
* @returns Promise with service object * @returns Promise with service object
*/ */
async fetch(request: ServiceFetchRequest): Promise<ServiceObject> { async fetch(request: ServiceFetchRequest, user?: string): Promise<ServiceObject> {
const response = await transceivePost<ServiceFetchRequest, ServiceFetchResponse>('service.fetch', request); const response = await transceivePost<ServiceFetchRequest, ServiceFetchResponse>('service.fetch', request, user);
return createServiceObject(response); return createServiceObject(response);
}, },
@@ -82,8 +82,8 @@ export const serviceService = {
* *
* @returns Promise with service availability status * @returns Promise with service availability status
*/ */
async extant(request: ServiceExtantRequest): Promise<ServiceExtantResponse> { async extant(request: ServiceExtantRequest, user?: string): Promise<ServiceExtantResponse> {
return await transceivePost<ServiceExtantRequest, ServiceExtantResponse>('service.extant', request); return await transceivePost<ServiceExtantRequest, ServiceExtantResponse>('service.extant', request, user);
}, },
/** /**
@@ -96,7 +96,8 @@ export const serviceService = {
*/ */
async discover( async discover(
request: ServiceDiscoverRequest, request: ServiceDiscoverRequest,
onService: (service: ServiceObject) => void onService: (service: ServiceObject) => void,
user?: string
): Promise<{ total: number }> { ): Promise<{ total: number }> {
return await transceiveStream<ServiceDiscoverRequest, ServiceDiscoverResponse>( return await transceiveStream<ServiceDiscoverRequest, ServiceDiscoverResponse>(
'service.discover', 'service.discover',
@@ -111,7 +112,8 @@ export const serviceService = {
location: service.location, location: service.location,
}; };
onService(createServiceObject(serviceData)); onService(createServiceObject(serviceData));
} },
user
); );
}, },
@@ -121,8 +123,8 @@ export const serviceService = {
* @param request - Service test request * @param request - Service test request
* @returns Promise with test results * @returns Promise with test results
*/ */
async test(request: ServiceTestRequest): Promise<ServiceTestResponse> { async test(request: ServiceTestRequest, user?: string): Promise<ServiceTestResponse> {
return await transceivePost<ServiceTestRequest, ServiceTestResponse>('service.test', request); return await transceivePost<ServiceTestRequest, ServiceTestResponse>('service.test', request, user);
}, },
/** /**
@@ -132,8 +134,8 @@ export const serviceService = {
* *
* @returns Promise with created service object * @returns Promise with created service object
*/ */
async create(request: ServiceCreateRequest): Promise<ServiceObject> { async create(request: ServiceCreateRequest, user?: string): Promise<ServiceObject> {
const response = await transceivePost<ServiceCreateRequest, ServiceCreateResponse>('service.create', request); const response = await transceivePost<ServiceCreateRequest, ServiceCreateResponse>('service.create', request, user);
return createServiceObject(response); return createServiceObject(response);
}, },
@@ -144,8 +146,8 @@ export const serviceService = {
* *
* @returns Promise with updated service object * @returns Promise with updated service object
*/ */
async update(request: ServiceUpdateRequest): Promise<ServiceObject> { async update(request: ServiceUpdateRequest, user?: string): Promise<ServiceObject> {
const response = await transceivePost<ServiceUpdateRequest, ServiceUpdateResponse>('service.update', request); const response = await transceivePost<ServiceUpdateRequest, ServiceUpdateResponse>('service.update', request, user);
return createServiceObject(response); return createServiceObject(response);
}, },
@@ -156,8 +158,8 @@ export const serviceService = {
* *
* @returns Promise with deletion result * @returns Promise with deletion result
*/ */
async delete(request: { provider: string; identifier: string | number }): Promise<any> { async delete(request: { provider: string; identifier: string | number }, user?: string): Promise<any> {
return await transceivePost<ServiceDeleteRequest, ServiceDeleteResponse>('service.delete', request); return await transceivePost<ServiceDeleteRequest, ServiceDeleteResponse>('service.delete', request, user);
}, },
}; };
+31 -18
View File
@@ -131,10 +131,10 @@ export const useServicesStore = defineStore('mailServicesStore', () => {
* *
* @returns Promise with service object list keyed by provider and service identifier * @returns Promise with service object list keyed by provider and service identifier
*/ */
async function list(targets?: ServiceIdentifier[] | CollectionIdentifier[]): Promise<Record<string, ServiceObject>> { async function list(targets?: ServiceIdentifier[] | CollectionIdentifier[], user?: string): Promise<Record<string, ServiceObject>> {
transceiving.value = true transceiving.value = true
try { try {
const response = await serviceService.list({ targets }) const response = await serviceService.list({ targets }, user)
// Flatten nested structure: provider-id: { service-id: object } -> "provider-id:service-id": object // Flatten nested structure: provider-id: { service-id: object } -> "provider-id:service-id": object
const services: Record<string, ServiceObject> = {} const services: Record<string, ServiceObject> = {}
@@ -145,8 +145,10 @@ export const useServicesStore = defineStore('mailServicesStore', () => {
}) })
}) })
// Merge retrieved services into state // Merge retrieved services into state (acting-user context stays out of the shared cache)
if (!user) {
_services.value = { ..._services.value, ...services } _services.value = { ..._services.value, ...services }
}
console.debug('[Mail Manager][Store] - Successfully retrieved', Object.keys(services).length, 'services') console.debug('[Mail Manager][Store] - Successfully retrieved', Object.keys(services).length, 'services')
return services return services
@@ -166,14 +168,16 @@ export const useServicesStore = defineStore('mailServicesStore', () => {
* *
* @returns Promise with service object * @returns Promise with service object
*/ */
async function fetch(provider: string, identifier: string | number): Promise<ServiceObject> { async function fetch(provider: string, identifier: string | number, user?: string): Promise<ServiceObject> {
transceiving.value = true transceiving.value = true
try { try {
const service = await serviceService.fetch({ provider, identifier }) const service = await serviceService.fetch({ provider, identifier }, user)
// Merge fetched service into state // Merge fetched service into state (acting-user context stays out of the shared cache)
const key = identifierKey(service.provider, service.identifier) const key = identifierKey(service.provider, service.identifier)
if (!user) {
_services.value[key] = service _services.value[key] = service
}
console.debug('[Mail Manager][Store] - Successfully fetched service:', key) console.debug('[Mail Manager][Store] - Successfully fetched service:', key)
return service return service
@@ -192,10 +196,10 @@ export const useServicesStore = defineStore('mailServicesStore', () => {
* *
* @returns Promise with service availability status * @returns Promise with service availability status
*/ */
async function extant(targets: ServiceIdentifier[]) { async function extant(targets: ServiceIdentifier[], user?: string) {
transceiving.value = true transceiving.value = true
try { try {
const response = await serviceService.extant({ targets }) const response = await serviceService.extant({ targets }, user)
console.debug('[Mail Manager][Store] - Successfully checked', targets?.length ?? 0, 'services') console.debug('[Mail Manager][Store] - Successfully checked', targets?.length ?? 0, 'services')
return response return response
@@ -215,14 +219,16 @@ export const useServicesStore = defineStore('mailServicesStore', () => {
* *
* @returns Promise with created service object * @returns Promise with created service object
*/ */
async function create(provider: string, data: Partial<ServiceInterface>): Promise<ServiceObject> { async function create(provider: string, data: Partial<ServiceInterface>, user?: string): Promise<ServiceObject> {
transceiving.value = true transceiving.value = true
try { try {
const service = await serviceService.create({ provider, data }) const service = await serviceService.create({ provider, data }, user)
// Merge created service into state // Merge created service into state (acting-user context stays out of the shared cache)
const key = identifierKey(service.provider, service.identifier) const key = identifierKey(service.provider, service.identifier)
if (!user) {
_services.value[key] = service _services.value[key] = service
}
console.debug('[Mail Manager][Store] - Successfully created service:', key) console.debug('[Mail Manager][Store] - Successfully created service:', key)
return service return service
@@ -244,7 +250,7 @@ export const useServicesStore = defineStore('mailServicesStore', () => {
* *
* @returns Promise with updated service object * @returns Promise with updated service object
*/ */
async function update(provider: string, identifier: string | number, delta: boolean, data: ServiceObject | Partial<ServiceInterface>): Promise<ServiceObject> { async function update(provider: string, identifier: string | number, delta: boolean, data: ServiceObject | Partial<ServiceInterface>, user?: string): Promise<ServiceObject> {
transceiving.value = true transceiving.value = true
try { try {
// convert ServiceObject to JSON if needed // convert ServiceObject to JSON if needed
@@ -255,11 +261,13 @@ export const useServicesStore = defineStore('mailServicesStore', () => {
payload = data payload = data
} }
const service = await serviceService.update({ provider, identifier, delta, data: payload }) const service = await serviceService.update({ provider, identifier, delta, data: payload }, user)
// Merge updated service into state // Merge updated service into state (acting-user context stays out of the shared cache)
const key = identifierKey(service.provider, service.identifier) const key = identifierKey(service.provider, service.identifier)
if (!user) {
_services.value[key] = service _services.value[key] = service
}
console.debug('[Mail Manager][Store] - Successfully updated service:', key) console.debug('[Mail Manager][Store] - Successfully updated service:', key)
return service return service
@@ -279,14 +287,16 @@ export const useServicesStore = defineStore('mailServicesStore', () => {
* *
* @returns Promise with deletion result * @returns Promise with deletion result
*/ */
async function remove(provider: string, identifier: string | number): Promise<any> { async function remove(provider: string, identifier: string | number, user?: string): Promise<any> {
transceiving.value = true transceiving.value = true
try { try {
await serviceService.delete({ provider, identifier }) await serviceService.delete({ provider, identifier }, user)
// Remove deleted service from state // Remove deleted service from state
const key = identifierKey(provider, identifier) const key = identifierKey(provider, identifier)
if (!user) {
delete _services.value[key] delete _services.value[key]
}
console.debug('[Mail Manager][Store] - Successfully deleted service:', key) console.debug('[Mail Manager][Store] - Successfully deleted service:', key)
} catch (error: any) { } catch (error: any) {
@@ -314,6 +324,7 @@ export const useServicesStore = defineStore('mailServicesStore', () => {
location: string | undefined, location: string | undefined,
provider: string | undefined, provider: string | undefined,
onService?: (service: ServiceObject) => void, onService?: (service: ServiceObject) => void,
user?: string,
): Promise<{ total: number }> { ): Promise<{ total: number }> {
transceiving.value = true transceiving.value = true
@@ -322,7 +333,8 @@ export const useServicesStore = defineStore('mailServicesStore', () => {
{ identity, secret, location, provider }, { identity, secret, location, provider },
(service: ServiceObject) => { (service: ServiceObject) => {
onService?.(service) onService?.(service)
} },
user
) )
console.debug('[Mail Manager][Store] - Successfully discovered', result.total, 'services') console.debug('[Mail Manager][Store] - Successfully discovered', result.total, 'services')
@@ -350,6 +362,7 @@ export const useServicesStore = defineStore('mailServicesStore', () => {
identifier?: string | number | null, identifier?: string | number | null,
location?: ServiceLocation | Location | null, location?: ServiceLocation | Location | null,
identity?: ServiceIdentity | Identity | null, identity?: ServiceIdentity | Identity | null,
user?: string,
): Promise<any> { ): Promise<any> {
transceiving.value = true transceiving.value = true
try { try {
@@ -372,7 +385,7 @@ export const useServicesStore = defineStore('mailServicesStore', () => {
identity = identity.toJson() identity = identity.toJson()
} }
const response = await serviceService.test({ provider, identifier, location, identity }) const response = await serviceService.test({ provider, identifier, location, identity }, user)
console.debug('[Mail Manager][Store] - Successfully tested service:', provider, identifier || location) console.debug('[Mail Manager][Store] - Successfully tested service:', provider, identifier || location)
return response return response