@helyx/bot 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +16 -1
- package/dist/auto-moderation-scanner-registry.d.ts +27 -0
- package/dist/auto-moderation-scanner-registry.js +172 -0
- package/dist/cloud-connector.js +3 -0
- package/dist/cloud-control-dispatcher.js +32 -0
- package/dist/command-path-contract.js +1 -0
- package/dist/control-permissions.d.ts +2 -2
- package/dist/control-permissions.js +4 -2
- package/dist/control-server.d.ts +72 -16
- package/dist/control-server.js +150 -250
- package/dist/dashboard-action-control.d.ts +25 -0
- package/dist/dashboard-action-control.js +73 -0
- package/dist/dashboard-action-routes.d.ts +9 -0
- package/dist/dashboard-action-routes.js +36 -0
- package/dist/dashboard-feature-control.d.ts +37 -0
- package/dist/dashboard-feature-control.js +206 -0
- package/dist/dashboard-module-projection.d.ts +9 -0
- package/dist/dashboard-module-projection.js +15 -0
- package/dist/dashboard-preview-import-routes.d.ts +6 -0
- package/dist/dashboard-preview-import-routes.js +121 -0
- package/dist/discord-analytics.d.ts +3 -0
- package/dist/discord-analytics.js +49 -5
- package/dist/discord-auto-moderation-service.d.ts +21 -0
- package/dist/discord-auto-moderation-service.js +425 -0
- package/dist/discord-guild-health.d.ts +2 -2
- package/dist/discord-member-enforcement-service.d.ts +26 -0
- package/dist/discord-member-enforcement-service.js +590 -0
- package/dist/discord-message-enforcement-service.d.ts +9 -0
- package/dist/discord-message-enforcement-service.js +102 -0
- package/dist/discord-violation-thread-service.d.ts +33 -0
- package/dist/discord-violation-thread-service.js +789 -0
- package/dist/ecko-evidence-receiver.d.ts +32 -0
- package/dist/ecko-evidence-receiver.js +173 -0
- package/dist/ecko-transport-auth.d.ts +19 -0
- package/dist/ecko-transport-auth.js +80 -0
- package/dist/ecko-transport-routes.d.ts +19 -0
- package/dist/ecko-transport-routes.js +130 -0
- package/dist/embedded-auto-moderation-pool.d.ts +46 -0
- package/dist/embedded-auto-moderation-pool.js +324 -0
- package/dist/embedded-auto-moderation-scanner.d.ts +34 -0
- package/dist/embedded-auto-moderation-scanner.js +263 -0
- package/dist/embedded-auto-moderation-worker-runtime.d.ts +37 -0
- package/dist/embedded-auto-moderation-worker-runtime.js +37 -0
- package/dist/embedded-auto-moderation-worker.d.ts +2 -0
- package/dist/embedded-auto-moderation-worker.js +13 -0
- package/dist/events.d.ts +5 -1
- package/dist/events.js +243 -4
- package/dist/guilds.d.ts +2 -2
- package/dist/guilds.js +14 -8
- package/dist/hosted-server-moderation.d.ts +16 -5
- package/dist/hosted-server-moderation.js +32 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +84 -14
- package/dist/moderation-action-registry.d.ts +11 -0
- package/dist/moderation-action-registry.js +375 -0
- package/dist/moderation-case-disassociation.d.ts +13 -0
- package/dist/moderation-case-disassociation.js +83 -0
- package/dist/moderation-platform-services.d.ts +27 -0
- package/dist/moderation-platform-services.js +47 -0
- package/dist/modules.d.ts +6 -0
- package/dist/modules.js +65 -3
- package/dist/privileged-message-metadata-cache.d.ts +35 -0
- package/dist/privileged-message-metadata-cache.js +70 -0
- package/dist/scheduled-task-runner.js +24 -1
- package/dist/shard-contracts.d.ts +151 -1
- package/dist/shard-contracts.js +142 -1
- package/dist/shard-coordinator.d.ts +1 -1
- package/dist/shard-ecko-scanner-registry.d.ts +38 -0
- package/dist/shard-ecko-scanner-registry.js +96 -0
- package/dist/shard-router.d.ts +23 -0
- package/dist/shard-router.js +69 -5
- package/dist/shard-supervisor.d.ts +11 -0
- package/dist/shard-supervisor.js +67 -4
- package/dist/shard-worker.d.ts +15 -1
- package/dist/shard-worker.js +95 -17
- package/dist/transient-violation-evidence.d.ts +39 -0
- package/dist/transient-violation-evidence.js +132 -0
- package/documentation/README.md +2 -0
- package/documentation/examples/.env.example +12 -0
- package/documentation/guides/self-hosting.md +17 -1
- package/documentation/modules/auto-moderation.md +51 -0
- package/documentation/modules/moderation.md +42 -0
- package/documentation/reference/environment.md +11 -0
- package/package.json +17 -5
package/dist/control-server.js
CHANGED
|
@@ -12,13 +12,18 @@ import { mutateDashboardModuleStatus, } from "./module-status-control.js";
|
|
|
12
12
|
import { canAccessModule, PREMIUM_MODULE_UNAVAILABLE_MESSAGE, } from "./premium-access.js";
|
|
13
13
|
import { completeFirstTimeCommandSetup, } from "./first-time-command-setup.js";
|
|
14
14
|
import { SHARD_HEALTH_STALE_MS, } from "./shard-contracts.js";
|
|
15
|
-
import { assertVersion,
|
|
15
|
+
import { assertVersion, declaredManagedResourcePermission, fromDashboardPolicy, managedResourcePermissionId, permissionPoliciesEqual, permissionPolicyKey, safeOptionalVersion, safeVersion, toDashboardPolicy, } from "./control-permissions.js";
|
|
16
16
|
import { assertPermissionMutationAllowed, BotControlForbiddenError, } from "./control-errors.js";
|
|
17
17
|
import { dashboardCommands } from "./dashboard-commands.js";
|
|
18
18
|
import { mapDashboardModule, } from "./dashboard-module-projection.js";
|
|
19
19
|
import { availabilityCatalogueDefinitions, availabilityCatalogue, dashboardModuleStatus, disabledModuleSummary, moduleAvailabilityService, moduleAvailabilityStates, statusCatalogueDefinitions, } from "./module-availability-control.js";
|
|
20
20
|
import { registerGuildCommandControlRoutes } from "./guild-command-control-routes.js";
|
|
21
21
|
import { createPrivacyProviderRegistry, describePrivacyProviders, } from "./privacy-provider-registry.js";
|
|
22
|
+
import { changedSettingFeatureIds, configurationPreviewFeatureIds, hasBoundedManagedResourceBytes, isBoundedManagedResourceValue, managedResourceColumnKeys, normalizeManagedResourceDetail, normalizeManagedResourcePage, normalizeManagedResourceRelationPage, projectManagedResourceObject, requireDashboardFeatures, resourceFieldFeatureIds, scalarInputFeatureIds, validateManagedResourceInput, } from "./dashboard-feature-control.js";
|
|
23
|
+
import { registerDashboardPreviewImportRoutes, registerDashboardResourceMutationRoutes, } from "./dashboard-preview-import-routes.js";
|
|
24
|
+
import { executeDashboardAction as executeDeclaredDashboardAction, previewDashboardAction as previewDeclaredDashboardAction, } from "./dashboard-action-control.js";
|
|
25
|
+
import { registerDashboardActionRoutes, } from "./dashboard-action-routes.js";
|
|
26
|
+
import { registerEckoTransportRoutes, } from "./ecko-transport-routes.js";
|
|
22
27
|
const snowflake = z.string().regex(/^\d{17,20}$/u);
|
|
23
28
|
const moduleId = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/u);
|
|
24
29
|
const MAX_DISCORD_SETTING_SELECTIONS = 25;
|
|
@@ -444,6 +449,7 @@ export class BotControlService {
|
|
|
444
449
|
const configurations = {};
|
|
445
450
|
const permissions = {};
|
|
446
451
|
const logging = {};
|
|
452
|
+
const featureAccess = {};
|
|
447
453
|
const loggingPolicies = new Map(storedLoggingPolicies.map((policy) => [
|
|
448
454
|
`${policy.sourceModuleId}:${policy.eventId}`,
|
|
449
455
|
policy,
|
|
@@ -466,6 +472,22 @@ export class BotControlService {
|
|
|
466
472
|
version: stored ? safeVersion(stored.version) : 0,
|
|
467
473
|
};
|
|
468
474
|
});
|
|
475
|
+
if (module.manifest.manifestVersion === "0.5" &&
|
|
476
|
+
module.manifest.features.length > 0) {
|
|
477
|
+
const access = this.#services.has(HELYX_SERVICE_NAMES.featureAccess)
|
|
478
|
+
? this.#services.get(HELYX_SERVICE_NAMES.featureAccess)
|
|
479
|
+
: undefined;
|
|
480
|
+
featureAccess[dashboardModule.id] = Object.fromEntries(await Promise.all(module.manifest.features.map(async (feature) => [
|
|
481
|
+
feature.id,
|
|
482
|
+
access
|
|
483
|
+
? (await access.check({
|
|
484
|
+
guildId,
|
|
485
|
+
moduleId: module.manifest.id,
|
|
486
|
+
featureId: feature.id,
|
|
487
|
+
})).decision
|
|
488
|
+
: "dependency_unhealthy",
|
|
489
|
+
])));
|
|
490
|
+
}
|
|
469
491
|
}
|
|
470
492
|
return {
|
|
471
493
|
discordCapabilities: {
|
|
@@ -473,6 +495,7 @@ export class BotControlService {
|
|
|
473
495
|
},
|
|
474
496
|
...resources,
|
|
475
497
|
modules: structuredClone(this.#dashboardModules),
|
|
498
|
+
featureAccess,
|
|
476
499
|
moduleStatuses,
|
|
477
500
|
globallyDisabledModuleIds: disabledModules.ids,
|
|
478
501
|
configurations,
|
|
@@ -652,6 +675,15 @@ export class BotControlService {
|
|
|
652
675
|
async updateConfiguration(input) {
|
|
653
676
|
const module = this.#requireDashboardModule(input.moduleId);
|
|
654
677
|
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
678
|
+
const settingsPermissionId = module.manifest.manifestVersion === "0.5"
|
|
679
|
+
? module.manifest.dashboard?.settingsMutationPermissionId
|
|
680
|
+
: undefined;
|
|
681
|
+
if (settingsPermissionId)
|
|
682
|
+
await this.#authorizeAction(input.guildId, module, input.actorUserId, settingsPermissionId);
|
|
683
|
+
if (module.manifest.manifestVersion === "0.5") {
|
|
684
|
+
const current = await this.#configurations.get(input.guildId, input.moduleId);
|
|
685
|
+
await requireDashboardFeatures(this.#services, input.guildId, input.moduleId, changedSettingFeatureIds(module.manifest.dashboard?.settings ?? [], current?.value ?? {}, input.value));
|
|
686
|
+
}
|
|
655
687
|
await this.#validateDiscordSettings(input.guildId, module, input.value);
|
|
656
688
|
if (module.configuration?.validateUpdate) {
|
|
657
689
|
try {
|
|
@@ -683,6 +715,25 @@ export class BotControlService {
|
|
|
683
715
|
});
|
|
684
716
|
return { value: saved.value, version: safeVersion(saved.version) };
|
|
685
717
|
}
|
|
718
|
+
async previewConfiguration(input) {
|
|
719
|
+
const module = this.#requireDashboardModule(input.moduleId);
|
|
720
|
+
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
721
|
+
const declaration = module.manifest.manifestVersion === "0.5"
|
|
722
|
+
? module.manifest.dashboard?.settingsPreview
|
|
723
|
+
: undefined;
|
|
724
|
+
if (!declaration || !module.configuration?.settingsPreview)
|
|
725
|
+
throw new BotControlNotFoundError("Module settings preview is not declared by this module");
|
|
726
|
+
const actor = await this.#authorizeAction(input.guildId, module, input.actorUserId, declaration.permissionId);
|
|
727
|
+
validateManagedResourceInput(declaration.inputs, input.input);
|
|
728
|
+
await requireDashboardFeatures(this.#services, input.guildId, input.moduleId, await configurationPreviewFeatureIds(this.#configurations, input.guildId, module, declaration, input));
|
|
729
|
+
const value = module.configuration.validate(input.value);
|
|
730
|
+
return managedResourceObject.parse(await module.configuration.settingsPreview({
|
|
731
|
+
guildId: input.guildId,
|
|
732
|
+
actor,
|
|
733
|
+
correlationId: input.correlationId,
|
|
734
|
+
services: this.#services,
|
|
735
|
+
}, { value, input: input.input }));
|
|
736
|
+
}
|
|
686
737
|
async updatePermissions(input) {
|
|
687
738
|
const module = this.#requireDashboardModule(input.moduleId);
|
|
688
739
|
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
@@ -856,31 +907,28 @@ export class BotControlService {
|
|
|
856
907
|
};
|
|
857
908
|
}
|
|
858
909
|
async executeDashboardAction(input) {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
new InstallationRepository(this.database).isModuleEnabled(input.guildId, input.moduleId),
|
|
868
|
-
this.#authorizeAction(input.guildId, module, input.actorUserId, dashboardActionPermissionId(module, input.actionId)),
|
|
869
|
-
]);
|
|
870
|
-
if (!enabled) {
|
|
871
|
-
throw new BotControlValidationError("Enable this module before running its dashboard actions");
|
|
872
|
-
}
|
|
873
|
-
return action.execute({
|
|
874
|
-
guildId: input.guildId,
|
|
875
|
-
actorUserId: input.actorUserId,
|
|
876
|
-
correlationId: input.correlationId,
|
|
910
|
+
return executeDeclaredDashboardAction(input, this.#dashboardActionControl());
|
|
911
|
+
}
|
|
912
|
+
async previewDashboardAction(input) {
|
|
913
|
+
return previewDeclaredDashboardAction(input, this.#dashboardActionControl());
|
|
914
|
+
}
|
|
915
|
+
#dashboardActionControl() {
|
|
916
|
+
return {
|
|
917
|
+
database: this.database,
|
|
877
918
|
services: this.#services,
|
|
919
|
+
requireDashboardModule: (moduleIdValue) => this.#requireDashboardModule(moduleIdValue),
|
|
920
|
+
requireModuleAccessible: (guildId, moduleIdValue) => this.#requireModuleAccessible(guildId, moduleIdValue),
|
|
921
|
+
authorizeAction: (guildId, module, actorUserId, permissionId) => this.#authorizeAction(guildId, module, actorUserId, permissionId),
|
|
878
922
|
sendToChannel: (channelId, response) => this.resources.sendToChannel(channelId, response),
|
|
879
923
|
deleteChannelMessage: (channelId, messageId) => this.resources.deleteChannelMessage(channelId, messageId),
|
|
880
|
-
|
|
924
|
+
notFound: (message) => new BotControlNotFoundError(message),
|
|
925
|
+
invalid: (message) => new BotControlValidationError(message),
|
|
926
|
+
};
|
|
881
927
|
}
|
|
882
928
|
async listManagedResources(input) {
|
|
883
929
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
930
|
+
const declaration = module.manifest.dashboard?.resources?.find(({ id }) => id === input.resourceType);
|
|
931
|
+
await requireDashboardFeatures(this.#services, input.guildId, input.moduleId, [declaration?.featureId]);
|
|
884
932
|
this.#requireManagedResourceOperation(module, input.resourceType, "list");
|
|
885
933
|
this.#validateManagedResourceQuery(module, input.resourceType, input);
|
|
886
934
|
const result = await resource.list(await this.#managedResourceContext(input, module, "list"), {
|
|
@@ -893,6 +941,8 @@ export class BotControlService {
|
|
|
893
941
|
}
|
|
894
942
|
async readManagedResource(input) {
|
|
895
943
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
944
|
+
const declaration = module.manifest.dashboard?.resources?.find(({ id }) => id === input.resourceType);
|
|
945
|
+
await requireDashboardFeatures(this.#services, input.guildId, input.moduleId, [declaration?.featureId]);
|
|
896
946
|
this.#requireManagedResourceOperation(module, input.resourceType, "read");
|
|
897
947
|
const result = await resource.read(await this.#managedResourceContext(input, module, "read"), input.resourceId);
|
|
898
948
|
if (!result)
|
|
@@ -907,6 +957,8 @@ export class BotControlService {
|
|
|
907
957
|
?.relations?.find(({ id }) => id === input.relationId);
|
|
908
958
|
if (!relation || !resource.listRelation)
|
|
909
959
|
throw new BotControlNotFoundError("Managed resource relation is not declared by this module");
|
|
960
|
+
const resourceDeclaration = module.manifest.dashboard?.resources?.find(({ id }) => id === input.resourceType);
|
|
961
|
+
await requireDashboardFeatures(this.#services, input.guildId, input.moduleId, [resourceDeclaration?.featureId, relation.featureId]);
|
|
910
962
|
const context = await this.#managedResourceContext(input, module, "read", relation.permissionId);
|
|
911
963
|
return normalizeManagedResourceRelationPage(managedResourceRelationPageSchema.parse(await resource.listRelation(context, input.resourceId, input.relationId, {
|
|
912
964
|
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
@@ -919,14 +971,44 @@ export class BotControlService {
|
|
|
919
971
|
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
920
972
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
921
973
|
this.#requireManagedResourceOperation(module, input.resourceType, "preview");
|
|
974
|
+
const declaration = module.manifest.dashboard?.resources?.find(({ id }) => id === input.resourceType);
|
|
975
|
+
await requireDashboardFeatures(this.#services, input.guildId, input.moduleId, [
|
|
976
|
+
declaration?.featureId,
|
|
977
|
+
...resourceFieldFeatureIds(declaration?.fields ?? [], input.value),
|
|
978
|
+
]);
|
|
922
979
|
return resource.preview(await this.#managedResourceContext(input, module, "preview"), input.value);
|
|
923
980
|
}
|
|
981
|
+
async importManagedResource(input) {
|
|
982
|
+
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
983
|
+
const { module, resource } = this.#prepareManagedResource(input);
|
|
984
|
+
this.#requireManagedResourceOperation(module, input.resourceType, "import");
|
|
985
|
+
const declaration = module.manifest.dashboard?.resources?.find(({ id }) => id === input.resourceType);
|
|
986
|
+
if (!declaration || !("import" in declaration) || !declaration.import)
|
|
987
|
+
throw new BotControlNotFoundError("Managed resource import is not declared by this module");
|
|
988
|
+
if (!resource.import)
|
|
989
|
+
throw new BotControlNotFoundError("Managed resource import is not implemented by this module");
|
|
990
|
+
if (Buffer.byteLength(input.content, "utf8") > declaration.import.maxBytes)
|
|
991
|
+
throw new BotControlValidationError("Managed resource import exceeds the declared byte limit");
|
|
992
|
+
await requireDashboardFeatures(this.#services, input.guildId, input.moduleId, [declaration.featureId, declaration.import.featureId]);
|
|
993
|
+
const result = await resource.import(await this.#managedResourceContext(input, module, "import"), { format: input.format, content: input.content });
|
|
994
|
+
const value = managedResourceObject.parse(result.value);
|
|
995
|
+
await requireDashboardFeatures(this.#services, input.guildId, input.moduleId, resourceFieldFeatureIds(declaration.fields, value));
|
|
996
|
+
return {
|
|
997
|
+
value: projectManagedResourceObject(value, declaration.fields.map(({ key }) => key)),
|
|
998
|
+
preview: managedResourceObject.parse(result.preview),
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
924
1001
|
async createManagedResource(input) {
|
|
925
1002
|
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
926
1003
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
927
1004
|
this.#requireManagedResourceOperation(module, input.resourceType, "create");
|
|
928
1005
|
const context = await this.#managedResourceContext(input, module, "create");
|
|
929
|
-
const
|
|
1006
|
+
const declaration = module.manifest.dashboard?.resources?.find(({ id }) => id === input.resourceType);
|
|
1007
|
+
await requireDashboardFeatures(this.#services, input.guildId, input.moduleId, [
|
|
1008
|
+
declaration?.featureId,
|
|
1009
|
+
...resourceFieldFeatureIds(declaration?.fields ?? [], input.value),
|
|
1010
|
+
]);
|
|
1011
|
+
const declaredIntents = declaration?.creationIntents ?? ["publish"];
|
|
930
1012
|
if (!declaredIntents.includes(input.intent))
|
|
931
1013
|
throw new DashboardActionValidationError("This creation intent is not declared by the module.");
|
|
932
1014
|
return normalizeManagedResourceDetail(managedResourceDetailSchema.parse(await resource.create(context, input.value, input.intent)), managedResourceColumnKeys(module, input.resourceType));
|
|
@@ -935,6 +1017,11 @@ export class BotControlService {
|
|
|
935
1017
|
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
936
1018
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
937
1019
|
this.#requireManagedResourceOperation(module, input.resourceType, "update");
|
|
1020
|
+
const declaration = module.manifest.dashboard?.resources?.find(({ id }) => id === input.resourceType);
|
|
1021
|
+
await requireDashboardFeatures(this.#services, input.guildId, input.moduleId, [
|
|
1022
|
+
declaration?.featureId,
|
|
1023
|
+
...resourceFieldFeatureIds(declaration?.fields ?? [], input.value),
|
|
1024
|
+
]);
|
|
938
1025
|
return normalizeManagedResourceDetail(managedResourceDetailSchema.parse(await resource.update(await this.#managedResourceContext(input, module, "update"), {
|
|
939
1026
|
resourceId: input.resourceId,
|
|
940
1027
|
expectedRevision: input.expectedRevision,
|
|
@@ -945,6 +1032,8 @@ export class BotControlService {
|
|
|
945
1032
|
await this.#requireModuleAccessible(input.guildId, input.moduleId);
|
|
946
1033
|
const { module, resource } = this.#prepareManagedResource(input);
|
|
947
1034
|
this.#requireManagedResourceOperation(module, input.resourceType, "delete");
|
|
1035
|
+
const declaration = module.manifest.dashboard?.resources?.find(({ id }) => id === input.resourceType);
|
|
1036
|
+
await requireDashboardFeatures(this.#services, input.guildId, input.moduleId, [declaration?.featureId]);
|
|
948
1037
|
return managedResourceDeleteResultSchema.parse(await resource.delete(await this.#managedResourceContext(input, module, "delete"), {
|
|
949
1038
|
resourceId: input.resourceId,
|
|
950
1039
|
expectedRevision: input.expectedRevision,
|
|
@@ -961,7 +1050,13 @@ export class BotControlService {
|
|
|
961
1050
|
throw new BotControlNotFoundError("Managed resource action is not declared by this module");
|
|
962
1051
|
const permissionId = managedResourcePermissionId(module, input.resourceType, "action", input.actionId, declared.permissionId);
|
|
963
1052
|
const context = await this.#managedResourceContext(input, module, "action", permissionId);
|
|
964
|
-
|
|
1053
|
+
validateManagedResourceInput(declared.inputs ?? [], input.value);
|
|
1054
|
+
const resourceDeclaration = module.manifest.dashboard?.resources?.find(({ id }) => id === input.resourceType);
|
|
1055
|
+
await requireDashboardFeatures(this.#services, input.guildId, input.moduleId, [
|
|
1056
|
+
resourceDeclaration?.featureId,
|
|
1057
|
+
declared.featureId,
|
|
1058
|
+
...scalarInputFeatureIds(declared.inputs ?? [], input.value),
|
|
1059
|
+
]);
|
|
965
1060
|
const result = await resource.executeAction(context, {
|
|
966
1061
|
resourceId: input.resourceId,
|
|
967
1062
|
actionId: input.actionId,
|
|
@@ -1207,9 +1302,13 @@ export async function createBotControlServer(input) {
|
|
|
1207
1302
|
requestTimeout: 15_000,
|
|
1208
1303
|
});
|
|
1209
1304
|
const expectedSecret = digest(input.secret);
|
|
1305
|
+
if (input.ecko)
|
|
1306
|
+
server.addContentTypeParser("application/ecko+json", { parseAs: "buffer", bodyLimit: 256 * 1_024 }, (_request, body, done) => done(null, body));
|
|
1210
1307
|
server.addHook("onRequest", async (request, reply) => {
|
|
1211
1308
|
if (request.url === "/health")
|
|
1212
1309
|
return;
|
|
1310
|
+
if (input.ecko && request.url.startsWith("/v1/ecko/"))
|
|
1311
|
+
return;
|
|
1213
1312
|
const authorization = request.headers.authorization;
|
|
1214
1313
|
const supplied = authorization?.startsWith("Bearer ")
|
|
1215
1314
|
? authorization.slice("Bearer ".length)
|
|
@@ -1240,7 +1339,27 @@ export async function createBotControlServer(input) {
|
|
|
1240
1339
|
}
|
|
1241
1340
|
server.get("/health", (_request, reply) => readiness(reply));
|
|
1242
1341
|
server.get("/v1/health", (_request, reply) => readiness(reply));
|
|
1342
|
+
server.get("/v1/runtime-configuration", (_request, reply) => reply.header("Cache-Control", "no-store").send({
|
|
1343
|
+
hostedModerationEnabled: input.runtimeConfiguration?.hostedModerationEnabled ?? false,
|
|
1344
|
+
}));
|
|
1345
|
+
server.post("/v1/hosted-server-moderation/leave/deliver", async (request) => {
|
|
1346
|
+
if (!input.deliverHostedServerLeave)
|
|
1347
|
+
throw new BotControlTransportError(503, "Hosted server moderation delivery is unavailable");
|
|
1348
|
+
const body = z
|
|
1349
|
+
.object({ operationId: z.uuid(), guildId: snowflake })
|
|
1350
|
+
.strict()
|
|
1351
|
+
.parse(request.body);
|
|
1352
|
+
return { outcome: await input.deliverHostedServerLeave(body) };
|
|
1353
|
+
});
|
|
1354
|
+
server.post("/v1/hosted-servers/:guildId/owner/refresh", async (request) => {
|
|
1355
|
+
if (!input.refreshHostedServerOwner)
|
|
1356
|
+
throw new BotControlTransportError(503, "Hosted server owner refresh is unavailable");
|
|
1357
|
+
const { guildId } = z.object({ guildId: snowflake }).parse(request.params);
|
|
1358
|
+
return input.refreshHostedServerOwner(guildId);
|
|
1359
|
+
});
|
|
1243
1360
|
registerGuildCommandControlRoutes(server, input.guildCommands, input.service);
|
|
1361
|
+
if (input.ecko)
|
|
1362
|
+
registerEckoTransportRoutes(server, input.ecko);
|
|
1244
1363
|
server.post("/v1/verification/results", async (request) => input.service.acceptVerificationResult(signedAssessmentEnvelopeSchema.parse(request.body)));
|
|
1245
1364
|
server.post("/v1/oauth/consumers/cleanup", async (request) => {
|
|
1246
1365
|
const body = z
|
|
@@ -1458,17 +1577,7 @@ export async function createBotControlServer(input) {
|
|
|
1458
1577
|
.parse(request.body);
|
|
1459
1578
|
return input.service.setModuleStatus({ ...params, ...body });
|
|
1460
1579
|
});
|
|
1461
|
-
server
|
|
1462
|
-
const params = z
|
|
1463
|
-
.object({
|
|
1464
|
-
guildId: snowflake,
|
|
1465
|
-
moduleId,
|
|
1466
|
-
actionId: z.string().regex(/^[a-z][a-z0-9_-]*$/u),
|
|
1467
|
-
})
|
|
1468
|
-
.parse(request.params);
|
|
1469
|
-
const body = mutationContext.parse(request.body);
|
|
1470
|
-
return input.service.executeDashboardAction({ ...params, ...body });
|
|
1471
|
-
});
|
|
1580
|
+
registerDashboardActionRoutes(server, input.service);
|
|
1472
1581
|
server.get("/v1/guilds/:guildId/modules/:moduleId/resources/:resourceType", async (request) => {
|
|
1473
1582
|
const params = z
|
|
1474
1583
|
.object({
|
|
@@ -1570,104 +1679,8 @@ export async function createBotControlServer(input) {
|
|
|
1570
1679
|
correlationId: query.correlationId,
|
|
1571
1680
|
});
|
|
1572
1681
|
});
|
|
1573
|
-
server
|
|
1574
|
-
|
|
1575
|
-
.object({
|
|
1576
|
-
guildId: snowflake,
|
|
1577
|
-
moduleId,
|
|
1578
|
-
resourceType: resourceActionId,
|
|
1579
|
-
})
|
|
1580
|
-
.strict()
|
|
1581
|
-
.parse(request.params);
|
|
1582
|
-
const body = mutationContext
|
|
1583
|
-
.extend({ value: managedResourceObject })
|
|
1584
|
-
.strict()
|
|
1585
|
-
.parse(request.body);
|
|
1586
|
-
return input.service.previewManagedResource({ ...params, ...body });
|
|
1587
|
-
});
|
|
1588
|
-
server.post("/v1/guilds/:guildId/modules/:moduleId/resources/:resourceType", async (request) => {
|
|
1589
|
-
const params = z
|
|
1590
|
-
.object({
|
|
1591
|
-
guildId: snowflake,
|
|
1592
|
-
moduleId,
|
|
1593
|
-
resourceType: resourceActionId,
|
|
1594
|
-
})
|
|
1595
|
-
.strict()
|
|
1596
|
-
.parse(request.params);
|
|
1597
|
-
const body = mutationContext
|
|
1598
|
-
.extend({
|
|
1599
|
-
intent: z.enum(["draft", "publish"]).optional().default("publish"),
|
|
1600
|
-
value: managedResourceObject,
|
|
1601
|
-
})
|
|
1602
|
-
.strict()
|
|
1603
|
-
.parse(request.body);
|
|
1604
|
-
return input.service.createManagedResource({ ...params, ...body });
|
|
1605
|
-
});
|
|
1606
|
-
server.put("/v1/guilds/:guildId/modules/:moduleId/resources/:resourceType/:resourceId", async (request) => {
|
|
1607
|
-
const params = z
|
|
1608
|
-
.object({
|
|
1609
|
-
guildId: snowflake,
|
|
1610
|
-
moduleId,
|
|
1611
|
-
resourceType: resourceActionId,
|
|
1612
|
-
resourceId,
|
|
1613
|
-
})
|
|
1614
|
-
.strict()
|
|
1615
|
-
.parse(request.params);
|
|
1616
|
-
const body = mutationContext
|
|
1617
|
-
.extend({
|
|
1618
|
-
expectedRevision: z.number().int().min(0),
|
|
1619
|
-
value: managedResourceObject.optional().default({}),
|
|
1620
|
-
})
|
|
1621
|
-
.strict()
|
|
1622
|
-
.parse(request.body);
|
|
1623
|
-
return input.service.updateManagedResource({ ...params, ...body });
|
|
1624
|
-
});
|
|
1625
|
-
server.delete("/v1/guilds/:guildId/modules/:moduleId/resources/:resourceType/:resourceId", async (request) => {
|
|
1626
|
-
const params = z
|
|
1627
|
-
.object({
|
|
1628
|
-
guildId: snowflake,
|
|
1629
|
-
moduleId,
|
|
1630
|
-
resourceType: resourceActionId,
|
|
1631
|
-
resourceId,
|
|
1632
|
-
})
|
|
1633
|
-
.strict()
|
|
1634
|
-
.parse(request.params);
|
|
1635
|
-
const body = mutationContext
|
|
1636
|
-
.extend({ expectedRevision: z.number().int().min(0) })
|
|
1637
|
-
.strict()
|
|
1638
|
-
.parse(request.body);
|
|
1639
|
-
return input.service.deleteManagedResource({ ...params, ...body });
|
|
1640
|
-
});
|
|
1641
|
-
server.post("/v1/guilds/:guildId/modules/:moduleId/resources/:resourceType/:resourceId/actions/:actionId", async (request) => {
|
|
1642
|
-
const params = z
|
|
1643
|
-
.object({
|
|
1644
|
-
guildId: snowflake,
|
|
1645
|
-
moduleId,
|
|
1646
|
-
resourceType: resourceActionId,
|
|
1647
|
-
resourceId,
|
|
1648
|
-
actionId: resourceActionId,
|
|
1649
|
-
})
|
|
1650
|
-
.strict()
|
|
1651
|
-
.parse(request.params);
|
|
1652
|
-
const body = mutationContext
|
|
1653
|
-
.extend({
|
|
1654
|
-
expectedRevision: z.number().int().min(0),
|
|
1655
|
-
value: managedResourceObject.optional().default({}),
|
|
1656
|
-
actorAuthenticatedAt: z.iso.datetime({ offset: true }).optional(),
|
|
1657
|
-
})
|
|
1658
|
-
.strict()
|
|
1659
|
-
.parse(request.body);
|
|
1660
|
-
return input.service.executeManagedResourceAction({
|
|
1661
|
-
...params,
|
|
1662
|
-
actorUserId: body.actorUserId,
|
|
1663
|
-
correlationId: body.correlationId,
|
|
1664
|
-
expectedRevision: body.expectedRevision,
|
|
1665
|
-
value: body.value,
|
|
1666
|
-
...(body.actorAuthenticatedAt === undefined
|
|
1667
|
-
? {}
|
|
1668
|
-
: { actorAuthenticatedAt: body.actorAuthenticatedAt }),
|
|
1669
|
-
});
|
|
1670
|
-
});
|
|
1682
|
+
registerDashboardPreviewImportRoutes(server, input.service);
|
|
1683
|
+
registerDashboardResourceMutationRoutes(server, input.service);
|
|
1671
1684
|
server.put("/v1/guilds/:guildId/modules/:moduleId/configuration", async (request) => {
|
|
1672
1685
|
const params = z
|
|
1673
1686
|
.object({ guildId: snowflake, moduleId })
|
|
@@ -1711,7 +1724,12 @@ export async function createBotControlServer(input) {
|
|
|
1711
1724
|
return reply.code(mapped.status).send({ message: mapped.message });
|
|
1712
1725
|
if (error instanceof BotControlTransportError)
|
|
1713
1726
|
return reply.code(error.status).send({ message: error.message });
|
|
1714
|
-
request.log.error({
|
|
1727
|
+
request.log.error({
|
|
1728
|
+
error: safeError(error),
|
|
1729
|
+
errorCode: typeof error.code === "string"
|
|
1730
|
+
? error.code
|
|
1731
|
+
: "unclassified",
|
|
1732
|
+
}, "Private bot control request failed");
|
|
1715
1733
|
return reply.code(500).send({ message: "The control request failed" });
|
|
1716
1734
|
});
|
|
1717
1735
|
return server;
|
|
@@ -1772,122 +1790,4 @@ function isUnknownMemberError(error) {
|
|
|
1772
1790
|
"code" in error &&
|
|
1773
1791
|
error.code === 10_007);
|
|
1774
1792
|
}
|
|
1775
|
-
function isBoundedManagedResourceValue(value, depth = 0, budget = { keys: 0 }) {
|
|
1776
|
-
if (depth > 8)
|
|
1777
|
-
return false;
|
|
1778
|
-
if (value === null ||
|
|
1779
|
-
typeof value === "boolean" ||
|
|
1780
|
-
(typeof value === "number" && Number.isFinite(value)))
|
|
1781
|
-
return true;
|
|
1782
|
-
if (typeof value === "string")
|
|
1783
|
-
return value.length <= 4_000;
|
|
1784
|
-
if (Array.isArray(value))
|
|
1785
|
-
return (value.length <= 100 &&
|
|
1786
|
-
value.every((item) => isBoundedManagedResourceValue(item, depth + 1, budget)));
|
|
1787
|
-
if (typeof value !== "object")
|
|
1788
|
-
return false;
|
|
1789
|
-
const prototype = Object.getPrototypeOf(value);
|
|
1790
|
-
if (prototype !== Object.prototype && prototype !== null)
|
|
1791
|
-
return false;
|
|
1792
|
-
const entries = Object.entries(value);
|
|
1793
|
-
budget.keys += entries.length;
|
|
1794
|
-
return (entries.length <= 100 &&
|
|
1795
|
-
budget.keys <= 500 &&
|
|
1796
|
-
entries.every(([key, item]) => key.length >= 1 &&
|
|
1797
|
-
key.length <= 100 &&
|
|
1798
|
-
isBoundedManagedResourceValue(item, depth + 1, budget)));
|
|
1799
|
-
}
|
|
1800
|
-
function hasBoundedManagedResourceBytes(value) {
|
|
1801
|
-
return (new TextEncoder().encode(JSON.stringify(value)).byteLength <= 48 * 1_024);
|
|
1802
|
-
}
|
|
1803
|
-
function normalizeManagedResourceDetail(value, attributeKeys) {
|
|
1804
|
-
return {
|
|
1805
|
-
id: value.id,
|
|
1806
|
-
revision: value.revision,
|
|
1807
|
-
status: value.status,
|
|
1808
|
-
title: value.title,
|
|
1809
|
-
...(value.description === undefined
|
|
1810
|
-
? {}
|
|
1811
|
-
: { description: value.description }),
|
|
1812
|
-
...(value.attributes === undefined || attributeKeys.length === 0
|
|
1813
|
-
? {}
|
|
1814
|
-
: {
|
|
1815
|
-
attributes: projectManagedResourceObject(value.attributes, attributeKeys),
|
|
1816
|
-
}),
|
|
1817
|
-
updatedAt: value.updatedAt,
|
|
1818
|
-
value: value.value,
|
|
1819
|
-
};
|
|
1820
|
-
}
|
|
1821
|
-
function normalizeManagedResourcePage(value, attributeKeys) {
|
|
1822
|
-
return {
|
|
1823
|
-
items: value.items.map((item) => ({
|
|
1824
|
-
id: item.id,
|
|
1825
|
-
revision: item.revision,
|
|
1826
|
-
status: item.status,
|
|
1827
|
-
title: item.title,
|
|
1828
|
-
...(item.description === undefined
|
|
1829
|
-
? {}
|
|
1830
|
-
: { description: item.description }),
|
|
1831
|
-
...(item.attributes === undefined || attributeKeys.length === 0
|
|
1832
|
-
? {}
|
|
1833
|
-
: {
|
|
1834
|
-
attributes: projectManagedResourceObject(item.attributes, attributeKeys),
|
|
1835
|
-
}),
|
|
1836
|
-
updatedAt: item.updatedAt,
|
|
1837
|
-
})),
|
|
1838
|
-
...(value.nextCursor === undefined ? {} : { nextCursor: value.nextCursor }),
|
|
1839
|
-
...(value.total === undefined ? {} : { total: value.total }),
|
|
1840
|
-
};
|
|
1841
|
-
}
|
|
1842
|
-
function normalizeManagedResourceRelationPage(value, valueKeys) {
|
|
1843
|
-
return {
|
|
1844
|
-
items: value.items.map((item) => ({
|
|
1845
|
-
id: item.id,
|
|
1846
|
-
value: projectManagedResourceObject(item.value, valueKeys),
|
|
1847
|
-
})),
|
|
1848
|
-
...(value.nextCursor === undefined ? {} : { nextCursor: value.nextCursor }),
|
|
1849
|
-
};
|
|
1850
|
-
}
|
|
1851
|
-
function projectManagedResourceObject(value, declaredKeys) {
|
|
1852
|
-
const allowed = new Set(declaredKeys);
|
|
1853
|
-
return Object.fromEntries(Object.entries(value)
|
|
1854
|
-
.filter(([key]) => allowed.has(key))
|
|
1855
|
-
.map(([key, item]) => [key, structuredClone(item)]));
|
|
1856
|
-
}
|
|
1857
|
-
function managedResourceColumnKeys(module, resourceType) {
|
|
1858
|
-
return (module.manifest.dashboard?.resources
|
|
1859
|
-
?.find(({ id }) => id === resourceType)
|
|
1860
|
-
?.columns?.map(({ key }) => key) ?? []);
|
|
1861
|
-
}
|
|
1862
|
-
function validateManagedResourceActionValue(inputs, value) {
|
|
1863
|
-
const declared = new Map(inputs.map((input) => [input.key, input]));
|
|
1864
|
-
for (const key of Object.keys(value))
|
|
1865
|
-
if (!declared.has(key))
|
|
1866
|
-
throw new DashboardActionValidationError(`The action value "${key}" is not declared by the module.`);
|
|
1867
|
-
for (const input of inputs) {
|
|
1868
|
-
const item = value[input.key];
|
|
1869
|
-
if (item === undefined) {
|
|
1870
|
-
if (input.required)
|
|
1871
|
-
throw new DashboardActionValidationError(`${input.label} is required for this action.`);
|
|
1872
|
-
continue;
|
|
1873
|
-
}
|
|
1874
|
-
const valid = input.type === "boolean"
|
|
1875
|
-
? typeof item === "boolean"
|
|
1876
|
-
: input.type === "integer"
|
|
1877
|
-
? typeof item === "number" &&
|
|
1878
|
-
Number.isInteger(item) &&
|
|
1879
|
-
item >= input.minimum &&
|
|
1880
|
-
item <= input.maximum
|
|
1881
|
-
: input.type === "select"
|
|
1882
|
-
? typeof item === "string" &&
|
|
1883
|
-
input.options.some(({ value: option }) => option === item)
|
|
1884
|
-
: input.type === "discord-channel" || input.type === "discord-role"
|
|
1885
|
-
? typeof item === "string" && snowflake.safeParse(item).success
|
|
1886
|
-
: typeof item === "string" &&
|
|
1887
|
-
item.length >= input.minLength &&
|
|
1888
|
-
item.length <= input.maxLength;
|
|
1889
|
-
if (!valid)
|
|
1890
|
-
throw new DashboardActionValidationError(`${input.label} is not a valid action value.`);
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
1793
|
//# sourceMappingURL=control-server.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type DatabasePool } from "@helyx/database";
|
|
2
|
+
import { type DashboardActionContext, type DashboardActionInspection, type DashboardActionResult, type ModuleDefinition, type PermissionActor, type ServiceAccess } from "@helyx/sdk";
|
|
3
|
+
export interface DashboardActionInput {
|
|
4
|
+
guildId: string;
|
|
5
|
+
moduleId: string;
|
|
6
|
+
actionId: string;
|
|
7
|
+
actorUserId: string;
|
|
8
|
+
correlationId: string;
|
|
9
|
+
inspectionRevision?: string;
|
|
10
|
+
}
|
|
11
|
+
export type DashboardActionPreviewInput = Omit<DashboardActionInput, "inspectionRevision">;
|
|
12
|
+
export interface DashboardActionControl {
|
|
13
|
+
database: DatabasePool;
|
|
14
|
+
services: ServiceAccess;
|
|
15
|
+
requireDashboardModule(moduleId: string): ModuleDefinition;
|
|
16
|
+
requireModuleAccessible(guildId: string, moduleId: string): Promise<void>;
|
|
17
|
+
authorizeAction(guildId: string, module: ModuleDefinition, actorUserId: string, permissionId: string): Promise<PermissionActor>;
|
|
18
|
+
sendToChannel: DashboardActionContext["sendToChannel"];
|
|
19
|
+
deleteChannelMessage: DashboardActionContext["deleteChannelMessage"];
|
|
20
|
+
notFound(message: string): Error;
|
|
21
|
+
invalid(message: string): Error;
|
|
22
|
+
}
|
|
23
|
+
export declare function executeDashboardAction(input: DashboardActionInput, control: DashboardActionControl): Promise<DashboardActionResult>;
|
|
24
|
+
export declare function previewDashboardAction(input: DashboardActionPreviewInput, control: DashboardActionControl): Promise<DashboardActionInspection>;
|
|
25
|
+
//# sourceMappingURL=dashboard-action-control.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { InstallationRepository } from "@helyx/database";
|
|
2
|
+
import {} from "@helyx/sdk";
|
|
3
|
+
import { dashboardActionPermissionId } from "./control-permissions.js";
|
|
4
|
+
import { requireDashboardFeatures } from "./dashboard-feature-control.js";
|
|
5
|
+
function prepareAction(input, control) {
|
|
6
|
+
const module = control.requireDashboardModule(input.moduleId);
|
|
7
|
+
const declared = module.manifest.dashboard?.actions?.find(({ id }) => id === input.actionId);
|
|
8
|
+
const action = module.dashboardActions?.find(({ id }) => id === input.actionId);
|
|
9
|
+
return { module, declared, action };
|
|
10
|
+
}
|
|
11
|
+
async function requireEnabledAndAuthorized(input, control, module, permissionId, disabledMessage) {
|
|
12
|
+
const [enabled] = await Promise.all([
|
|
13
|
+
new InstallationRepository(control.database).isModuleEnabled(input.guildId, input.moduleId),
|
|
14
|
+
control.authorizeAction(input.guildId, module, input.actorUserId, permissionId),
|
|
15
|
+
]);
|
|
16
|
+
if (!enabled)
|
|
17
|
+
throw control.invalid(disabledMessage);
|
|
18
|
+
}
|
|
19
|
+
function actionContext(input, control, module, permissionId) {
|
|
20
|
+
return {
|
|
21
|
+
guildId: input.guildId,
|
|
22
|
+
actorUserId: input.actorUserId,
|
|
23
|
+
correlationId: input.correlationId,
|
|
24
|
+
services: control.services,
|
|
25
|
+
revalidate: async () => {
|
|
26
|
+
try {
|
|
27
|
+
await control.requireModuleAccessible(input.guildId, input.moduleId);
|
|
28
|
+
const enabled = await new InstallationRepository(control.database).isModuleEnabled(input.guildId, input.moduleId);
|
|
29
|
+
if (!enabled)
|
|
30
|
+
return false;
|
|
31
|
+
await control.authorizeAction(input.guildId, module, input.actorUserId, permissionId);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
sendToChannel: control.sendToChannel,
|
|
39
|
+
deleteChannelMessage: control.deleteChannelMessage,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export async function executeDashboardAction(input, control) {
|
|
43
|
+
const { module, declared, action } = prepareAction(input, control);
|
|
44
|
+
await control.requireModuleAccessible(input.guildId, input.moduleId);
|
|
45
|
+
if (!declared || !action)
|
|
46
|
+
throw control.notFound("Dashboard action is not declared by this module");
|
|
47
|
+
await requireDashboardFeatures(control.services, input.guildId, input.moduleId, [declared.featureId]);
|
|
48
|
+
if (declared.inspection === "required" &&
|
|
49
|
+
(typeof input.inspectionRevision !== "string" ||
|
|
50
|
+
input.inspectionRevision.length < 1 ||
|
|
51
|
+
input.inspectionRevision.length > 200))
|
|
52
|
+
throw control.invalid("Run the action inspection before applying this change");
|
|
53
|
+
const permissionId = dashboardActionPermissionId(module, input.actionId, declared.permissionId);
|
|
54
|
+
await requireEnabledAndAuthorized(input, control, module, permissionId, "Enable this module before running its dashboard actions");
|
|
55
|
+
return action.execute(actionContext(input, control, module, permissionId), input.inspectionRevision
|
|
56
|
+
? { inspectionRevision: input.inspectionRevision }
|
|
57
|
+
: undefined);
|
|
58
|
+
}
|
|
59
|
+
export async function previewDashboardAction(input, control) {
|
|
60
|
+
const { module, declared, action } = prepareAction(input, control);
|
|
61
|
+
await control.requireModuleAccessible(input.guildId, input.moduleId);
|
|
62
|
+
if (!declared || !action?.inspect || !declared.inspection)
|
|
63
|
+
throw control.notFound("Dashboard action inspection is not declared by this module");
|
|
64
|
+
await requireDashboardFeatures(control.services, input.guildId, input.moduleId, [declared.featureId]);
|
|
65
|
+
const permissionId = dashboardActionPermissionId(module, input.actionId, declared.permissionId);
|
|
66
|
+
await requireEnabledAndAuthorized(input, control, module, permissionId, "Enable this module before inspecting its dashboard actions");
|
|
67
|
+
const result = await action.inspect(actionContext(input, control, module, permissionId));
|
|
68
|
+
if (result.inspectionRevision.length < 1 ||
|
|
69
|
+
result.inspectionRevision.length > 200)
|
|
70
|
+
throw new Error("Dashboard action returned an invalid inspection revision");
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=dashboard-action-control.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DashboardActionInspection, DashboardActionResult } from "@helyx/sdk";
|
|
2
|
+
import type { FastifyInstance } from "fastify";
|
|
3
|
+
import type { DashboardActionInput, DashboardActionPreviewInput } from "./dashboard-action-control.js";
|
|
4
|
+
export interface DashboardActionHandler {
|
|
5
|
+
executeDashboardAction(input: DashboardActionInput): Promise<DashboardActionResult>;
|
|
6
|
+
previewDashboardAction(input: DashboardActionPreviewInput): Promise<DashboardActionInspection>;
|
|
7
|
+
}
|
|
8
|
+
export declare function registerDashboardActionRoutes(server: FastifyInstance, service: DashboardActionHandler): void;
|
|
9
|
+
//# sourceMappingURL=dashboard-action-routes.d.ts.map
|