@memberjunction/core-entities 6.2.0-edge.3 → 6.2.0-edge.5

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.
Files changed (66) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/custom/ConversationForkAccess.d.ts +15 -0
  3. package/dist/custom/ConversationForkAccess.d.ts.map +1 -0
  4. package/dist/custom/ConversationForkAccess.js +28 -0
  5. package/dist/custom/ConversationForkAccess.js.map +1 -0
  6. package/dist/custom/ConversationWriteAccess.d.ts +21 -0
  7. package/dist/custom/ConversationWriteAccess.d.ts.map +1 -0
  8. package/dist/custom/ConversationWriteAccess.js +34 -0
  9. package/dist/custom/ConversationWriteAccess.js.map +1 -0
  10. package/dist/custom/MJConversationBranchEntityExtended.d.ts +30 -0
  11. package/dist/custom/MJConversationBranchEntityExtended.d.ts.map +1 -0
  12. package/dist/custom/MJConversationBranchEntityExtended.js +120 -0
  13. package/dist/custom/MJConversationBranchEntityExtended.js.map +1 -0
  14. package/dist/custom/MJConversationDetailEntityExtended.d.ts +46 -4
  15. package/dist/custom/MJConversationDetailEntityExtended.d.ts.map +1 -1
  16. package/dist/custom/MJConversationDetailEntityExtended.js +194 -47
  17. package/dist/custom/MJConversationDetailEntityExtended.js.map +1 -1
  18. package/dist/custom/MJDashboardEntityExtended.d.ts.map +1 -1
  19. package/dist/custom/MJDashboardEntityExtended.js +3 -0
  20. package/dist/custom/MJDashboardEntityExtended.js.map +1 -1
  21. package/dist/custom/MJThemeEntityExtended.d.ts +13 -0
  22. package/dist/custom/MJThemeEntityExtended.d.ts.map +1 -0
  23. package/dist/custom/MJThemeEntityExtended.js +37 -0
  24. package/dist/custom/MJThemeEntityExtended.js.map +1 -0
  25. package/dist/custom/ResourcePermissions/ResourcePermissionEngine.d.ts.map +1 -1
  26. package/dist/custom/ResourcePermissions/ResourcePermissionEngine.js +14 -13
  27. package/dist/custom/ResourcePermissions/ResourcePermissionEngine.js.map +1 -1
  28. package/dist/engines/FileStorageEngine.d.ts +40 -0
  29. package/dist/engines/FileStorageEngine.d.ts.map +1 -1
  30. package/dist/engines/FileStorageEngine.js +49 -0
  31. package/dist/engines/FileStorageEngine.js.map +1 -1
  32. package/dist/engines/InstanceConfigEngine.d.ts.map +1 -1
  33. package/dist/engines/InstanceConfigEngine.js +13 -3
  34. package/dist/engines/InstanceConfigEngine.js.map +1 -1
  35. package/dist/engines/PermissionEngine.d.ts +5 -4
  36. package/dist/engines/PermissionEngine.d.ts.map +1 -1
  37. package/dist/engines/PermissionEngine.js +7 -5
  38. package/dist/engines/PermissionEngine.js.map +1 -1
  39. package/dist/engines/QueryEngine.d.ts +81 -6
  40. package/dist/engines/QueryEngine.d.ts.map +1 -1
  41. package/dist/engines/QueryEngine.js +283 -4
  42. package/dist/engines/QueryEngine.js.map +1 -1
  43. package/dist/engines/UserInfoEngine.d.ts +64 -12
  44. package/dist/engines/UserInfoEngine.d.ts.map +1 -1
  45. package/dist/engines/UserInfoEngine.js +100 -33
  46. package/dist/engines/UserInfoEngine.js.map +1 -1
  47. package/dist/engines/artifacts.d.ts.map +1 -1
  48. package/dist/engines/artifacts.js +1 -0
  49. package/dist/engines/artifacts.js.map +1 -1
  50. package/dist/engines/conversations.d.ts +378 -35
  51. package/dist/engines/conversations.d.ts.map +1 -1
  52. package/dist/engines/conversations.js +736 -79
  53. package/dist/engines/conversations.js.map +1 -1
  54. package/dist/generated/entities/__mj.d.ts +3488 -230
  55. package/dist/generated/entities/__mj.d.ts.map +1 -1
  56. package/dist/generated/entities/__mj.js +2927 -18
  57. package/dist/generated/entities/__mj.js.map +1 -1
  58. package/dist/generated/remote_operations.d.ts +17 -5
  59. package/dist/generated/remote_operations.d.ts.map +1 -1
  60. package/dist/generated/remote_operations.js.map +1 -1
  61. package/dist/index.d.ts +4 -0
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +4 -0
  64. package/dist/index.js.map +1 -1
  65. package/package.json +6 -5
  66. package/readme.md +1 -1
@@ -0,0 +1,15 @@
1
+ import { type IMetadataProvider, type UserInfo } from '@memberjunction/core';
2
+ /** The MJ Authorization that lets a person start a fork of a conversation (Fork from here, Edit or Regenerate as a fork). */
3
+ export declare const CONVERSATIONS_FORK_AUTHORIZATION = "Conversations: Fork";
4
+ /**
5
+ * True when `user` holds {@link CONVERSATIONS_FORK_AUTHORIZATION} through a role: an Allow grant and
6
+ * no Deny on any of the user's roles (a grant of a parent authorization counts). False without a user
7
+ * or a provider, and when the authorization is missing from the provider's metadata or inactive, so a
8
+ * deployment that has not synced it fails closed.
9
+ */
10
+ export declare function UserCanFork(user: UserInfo | null | undefined, provider: IMetadataProvider | null | undefined): boolean;
11
+ /** The `MJ: User Settings` key of a person's forking switch; the value `off` turns forking off for them. */
12
+ export declare const CONVERSATIONS_FORKING_SETTING_KEY = "mj.conversations.forking.v1";
13
+ /** True unless the stored forking setting is `off`: no setting, an empty one or `on` mean forking is on. */
14
+ export declare function IsForkingSettingOn(value: string | null | undefined): boolean;
15
+ //# sourceMappingURL=ConversationForkAccess.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationForkAccess.d.ts","sourceRoot":"","sources":["../../src/custom/ConversationForkAccess.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,iBAAiB,EAAE,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErG,6HAA6H;AAC7H,eAAO,MAAM,gCAAgC,wBAAwB,CAAC;AAEtE;;;;;GAKG;AACH,wBAAgB,WAAW,CACvB,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EACjC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,GAAG,SAAS,GAC/C,OAAO,CAWT;AAED,4GAA4G;AAC5G,eAAO,MAAM,iCAAiC,gCAAgC,CAAC;AAE/E,4GAA4G;AAC5G,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAE5E"}
@@ -0,0 +1,28 @@
1
+ import { AuthorizationEvaluator } from '@memberjunction/core';
2
+ /** The MJ Authorization that lets a person start a fork of a conversation (Fork from here, Edit or Regenerate as a fork). */
3
+ export const CONVERSATIONS_FORK_AUTHORIZATION = 'Conversations: Fork';
4
+ /**
5
+ * True when `user` holds {@link CONVERSATIONS_FORK_AUTHORIZATION} through a role: an Allow grant and
6
+ * no Deny on any of the user's roles (a grant of a parent authorization counts). False without a user
7
+ * or a provider, and when the authorization is missing from the provider's metadata or inactive, so a
8
+ * deployment that has not synced it fails closed.
9
+ */
10
+ export function UserCanFork(user, provider) {
11
+ if (!user || !provider) {
12
+ return false;
13
+ }
14
+ const authorizations = provider.Authorizations ?? [];
15
+ const wanted = CONVERSATIONS_FORK_AUTHORIZATION.toLowerCase();
16
+ const grant = authorizations.find((a) => a.Name?.trim().toLowerCase() === wanted);
17
+ if (!grant) {
18
+ return false;
19
+ }
20
+ return new AuthorizationEvaluator().UserCanExecuteWithAncestors(grant, user, authorizations);
21
+ }
22
+ /** The `MJ: User Settings` key of a person's forking switch; the value `off` turns forking off for them. */
23
+ export const CONVERSATIONS_FORKING_SETTING_KEY = 'mj.conversations.forking.v1';
24
+ /** True unless the stored forking setting is `off`: no setting, an empty one or `on` mean forking is on. */
25
+ export function IsForkingSettingOn(value) {
26
+ return (value ?? '').trim().toLowerCase() !== 'off';
27
+ }
28
+ //# sourceMappingURL=ConversationForkAccess.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationForkAccess.js","sourceRoot":"","sources":["../../src/custom/ConversationForkAccess.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAyC,MAAM,sBAAsB,CAAC;AAErG,6HAA6H;AAC7H,MAAM,CAAC,MAAM,gCAAgC,GAAG,qBAAqB,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACvB,IAAiC,EACjC,QAA8C;IAE9C,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,MAAM,cAAc,GAAG,QAAQ,CAAC,cAAc,IAAI,EAAE,CAAC;IACrD,MAAM,MAAM,GAAG,gCAAgC,CAAC,WAAW,EAAE,CAAC;IAC9D,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,CAAC;IAClF,IAAI,CAAC,KAAK,EAAE,CAAC;QACT,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,sBAAsB,EAAE,CAAC,2BAA2B,CAAC,KAAK,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;AACjG,CAAC;AAED,4GAA4G;AAC5G,MAAM,CAAC,MAAM,iCAAiC,GAAG,6BAA6B,CAAC;AAE/E,4GAA4G;AAC5G,MAAM,UAAU,kBAAkB,CAAC,KAAgC;IAC/D,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC;AACxD,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { IMetadataProvider, UserInfo } from '@memberjunction/core';
2
+ /** Resource type ID for conversations in the resource permission system. */
3
+ export declare const CONVERSATIONS_RESOURCE_TYPE_ID = "81D4BC3D-9FEB-EF11-B01A-286B35C04427";
4
+ export interface ConversationWriteDecision {
5
+ Allowed: boolean;
6
+ Reason: string | null;
7
+ /** True only when the conversation loaded and the user is its owner. */
8
+ IsOwner: boolean;
9
+ /**
10
+ * True when the conversation loaded for the user. A conversation that did not load is `Allowed` whether it is
11
+ * missing or hidden from the user by a row filter, so a caller that must tell those apart checks this.
12
+ */
13
+ Loaded: boolean;
14
+ }
15
+ /**
16
+ * Whether a user may write rows that belong to a conversation: the owner, or a user with
17
+ * an Edit or Owner grant. Server-side only; a non-database provider trusts the enforcement
18
+ * that already ran upstream.
19
+ */
20
+ export declare function UserMayWriteConversation(provider: IMetadataProvider, user: UserInfo | null | undefined, conversationId: string): Promise<ConversationWriteDecision>;
21
+ //# sourceMappingURL=ConversationWriteAccess.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationWriteAccess.d.ts","sourceRoot":"","sources":["../../src/custom/ConversationWriteAccess.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAKnE,4EAA4E;AAC5E,eAAO,MAAM,8BAA8B,yCAAyC,CAAC;AAErF,MAAM,WAAW,yBAAyB;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,wEAAwE;IACxE,OAAO,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,MAAM,EAAE,OAAO,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC1C,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,EACjC,cAAc,EAAE,MAAM,GACvB,OAAO,CAAC,yBAAyB,CAAC,CAuBpC"}
@@ -0,0 +1,34 @@
1
+ import { UUIDsEqual } from '@memberjunction/global';
2
+ import { ResourcePermissionEngine } from './ResourcePermissions/ResourcePermissionEngine.js';
3
+ /** Resource type ID for conversations in the resource permission system. */
4
+ export const CONVERSATIONS_RESOURCE_TYPE_ID = '81D4BC3D-9FEB-EF11-B01A-286B35C04427';
5
+ /**
6
+ * Whether a user may write rows that belong to a conversation: the owner, or a user with
7
+ * an Edit or Owner grant. Server-side only; a non-database provider trusts the enforcement
8
+ * that already ran upstream.
9
+ */
10
+ export async function UserMayWriteConversation(provider, user, conversationId) {
11
+ if (provider?.ProviderType !== 'Database' || !user) {
12
+ return { Allowed: true, Reason: null, IsOwner: false, Loaded: false };
13
+ }
14
+ const conversation = await provider.GetEntityObject('MJ: Conversations', user);
15
+ if (!(await conversation.Load(conversationId))) {
16
+ return { Allowed: true, Reason: null, IsOwner: false, Loaded: false };
17
+ }
18
+ if (conversation.UserID && UUIDsEqual(conversation.UserID, user.ID)) {
19
+ return { Allowed: true, Reason: null, IsOwner: true, Loaded: true };
20
+ }
21
+ const engine = ResourcePermissionEngine.GetProviderInstance(provider, ResourcePermissionEngine);
22
+ await engine.Config(false, user);
23
+ const grant = engine
24
+ .GetUserAvailableResources(user, CONVERSATIONS_RESOURCE_TYPE_ID)
25
+ .find((p) => UUIDsEqual(p.ResourceRecordID, conversationId));
26
+ if (!grant) {
27
+ return { Allowed: false, Reason: 'You do not have access to this conversation.', IsOwner: false, Loaded: true };
28
+ }
29
+ if (grant.PermissionLevel === 'Edit' || grant.PermissionLevel === 'Owner') {
30
+ return { Allowed: true, Reason: null, IsOwner: false, Loaded: true };
31
+ }
32
+ return { Allowed: false, Reason: 'You have view-only access to this conversation.', IsOwner: false, Loaded: true };
33
+ }
34
+ //# sourceMappingURL=ConversationWriteAccess.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationWriteAccess.js","sourceRoot":"","sources":["../../src/custom/ConversationWriteAccess.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gDAAgD,CAAC;AAE1F,4EAA4E;AAC5E,MAAM,CAAC,MAAM,8BAA8B,GAAG,sCAAsC,CAAC;AAcrF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC1C,QAA2B,EAC3B,IAAiC,EACjC,cAAsB;IAEtB,IAAI,QAAQ,EAAE,YAAY,KAAK,UAAU,IAAI,CAAC,IAAI,EAAE,CAAC;QACjD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC1E,CAAC;IACD,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,eAAe,CAAuB,mBAAmB,EAAE,IAAI,CAAC,CAAC;IACrG,IAAI,CAAC,CAAC,MAAM,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC;QAC7C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC1E,CAAC;IACD,IAAI,YAAY,CAAC,MAAM,IAAI,UAAU,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QAClE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACxE,CAAC;IACD,MAAM,MAAM,GAAG,wBAAwB,CAAC,mBAAmB,CAA2B,QAAQ,EAAE,wBAAwB,CAA6B,CAAC;IACtJ,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACjC,MAAM,KAAK,GAAG,MAAM;SACf,yBAAyB,CAAC,IAAI,EAAE,8BAA8B,CAAC;SAC/D,IAAI,CAAC,CAAC,CAA6B,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC,CAAC;IAC7F,IAAI,CAAC,KAAK,EAAE,CAAC;QACT,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,8CAA8C,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACpH,CAAC;IACD,IAAI,KAAK,CAAC,eAAe,KAAK,MAAM,IAAI,KAAK,CAAC,eAAe,KAAK,OAAO,EAAE,CAAC;QACxE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACzE,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,iDAAiD,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACvH,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { EntityDeleteOptions, EntitySaveOptions } from '@memberjunction/core';
2
+ import { MJConversationBranchEntity } from '../generated/entity_subclasses.js';
3
+ /**
4
+ * Branch rows (forks) follow the message write rule: owner or Edit/Owner grant. Creating one on the
5
+ * server also needs the `Conversations: Fork` authorization; a save with no context user is trusted.
6
+ *
7
+ * Delete is refused while the fork still has its own `ConversationDetail` rows or child forks: the
8
+ * generated delete procedure clears `BranchID` on those rows and `ParentBranchID` on those children,
9
+ * which would move them into Main. Deleting a conversation does not use this class; its delete
10
+ * procedure removes the forks and rows in SQL.
11
+ */
12
+ export declare class MJConversationBranchEntityExtended extends MJConversationBranchEntity {
13
+ Save(options?: EntitySaveOptions): Promise<boolean>;
14
+ Delete(options?: EntityDeleteOptions): Promise<boolean>;
15
+ /**
16
+ * True when this save may create a fork. On a database provider with a context user, that user must
17
+ * hold `Conversations: Fork`; records the refusal otherwise. Other providers leave the check to the
18
+ * server, and a save with no context user is trusted.
19
+ */
20
+ private contextUserMayFork;
21
+ private currentUserMayWrite;
22
+ /**
23
+ * True when no `ConversationDetail` row is on this fork and no fork names it as its parent.
24
+ * Records the refusal and returns false otherwise, and when either count cannot be read.
25
+ */
26
+ private hasNoRowsOrChildBranches;
27
+ private recordDenied;
28
+ }
29
+ export declare function LoadMJConversationBranchEntityExtended(): void;
30
+ //# sourceMappingURL=MJConversationBranchEntityExtended.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MJConversationBranchEntityExtended.d.ts","sourceRoot":"","sources":["../../src/custom/MJConversationBranchEntityExtended.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,mBAAmB,EAAE,iBAAiB,EAAwC,MAAM,sBAAsB,CAAC;AAElJ,OAAO,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAa5E;;;;;;;;GAQG;AACH,qBACa,kCAAmC,SAAQ,0BAA0B;IAC/D,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAUnD,MAAM,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;IAUtE;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;YAaZ,mBAAmB;IAkBjC;;;OAGG;YACW,wBAAwB;IA2BtC,OAAO,CAAC,YAAY;CASvB;AAED,wBAAgB,sCAAsC,IAAI,IAAI,CAAG"}
@@ -0,0 +1,120 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { BaseEntity, BaseEntityResult, LogError, RunView } from '@memberjunction/core';
8
+ import { EscapeSQLString, RegisterClass } from '@memberjunction/global';
9
+ import { MJConversationBranchEntity } from '../generated/entity_subclasses.js';
10
+ import { UserMayWriteConversation } from './ConversationWriteAccess.js';
11
+ import { UserCanFork } from './ConversationForkAccess.js';
12
+ /** The refusal recorded when a person without the `Conversations: Fork` authorization creates a fork. */
13
+ const FORK_CREATE_REFUSED_MESSAGE = 'You do not have permission to fork conversations.';
14
+ /** The refusal recorded when a fork that still has messages or child forks is deleted. */
15
+ const FORK_DELETE_REFUSED_MESSAGE = 'A fork that has messages or forks under it cannot be deleted.';
16
+ /** The refusal recorded when the delete guard cannot read the fork's messages or child forks. */
17
+ const FORK_DELETE_CHECK_FAILED_MESSAGE = 'Unable to check whether the fork has messages or forks under it.';
18
+ /**
19
+ * Branch rows (forks) follow the message write rule: owner or Edit/Owner grant. Creating one on the
20
+ * server also needs the `Conversations: Fork` authorization; a save with no context user is trusted.
21
+ *
22
+ * Delete is refused while the fork still has its own `ConversationDetail` rows or child forks: the
23
+ * generated delete procedure clears `BranchID` on those rows and `ParentBranchID` on those children,
24
+ * which would move them into Main. Deleting a conversation does not use this class; its delete
25
+ * procedure removes the forks and rows in SQL.
26
+ */
27
+ let MJConversationBranchEntityExtended = class MJConversationBranchEntityExtended extends MJConversationBranchEntity {
28
+ async Save(options) {
29
+ if (!this.IsSaved && !this.contextUserMayFork()) {
30
+ return false;
31
+ }
32
+ if (!(await this.currentUserMayWrite(this.IsSaved ? 'update' : 'create'))) {
33
+ return false;
34
+ }
35
+ return super.Save(options);
36
+ }
37
+ async Delete(options) {
38
+ if (!(await this.currentUserMayWrite('delete'))) {
39
+ return false;
40
+ }
41
+ if (!(await this.hasNoRowsOrChildBranches())) {
42
+ return false;
43
+ }
44
+ return super.Delete(options);
45
+ }
46
+ /**
47
+ * True when this save may create a fork. On a database provider with a context user, that user must
48
+ * hold `Conversations: Fork`; records the refusal otherwise. Other providers leave the check to the
49
+ * server, and a save with no context user is trusted.
50
+ */
51
+ contextUserMayFork() {
52
+ const provider = this.ProviderToUse;
53
+ const user = this.ContextCurrentUser;
54
+ if (provider?.ProviderType !== 'Database' || !user) {
55
+ return true;
56
+ }
57
+ if (UserCanFork(user, provider)) {
58
+ return true;
59
+ }
60
+ this.recordDenied(FORK_CREATE_REFUSED_MESSAGE, 'create');
61
+ return false;
62
+ }
63
+ async currentUserMayWrite(resultType) {
64
+ try {
65
+ const decision = await UserMayWriteConversation(this.ProviderToUse, this.ContextCurrentUser, this.ConversationID);
66
+ if (!decision.Allowed) {
67
+ this.recordDenied(decision.Reason ?? 'Permission denied.', resultType);
68
+ }
69
+ return decision.Allowed;
70
+ }
71
+ catch (error) {
72
+ LogError(`MJConversationBranchEntityExtended: permission check failed for conversation ${this.ConversationID}: ${error instanceof Error ? error.message : String(error)}`);
73
+ this.recordDenied('Unable to verify conversation permissions.', resultType);
74
+ return false;
75
+ }
76
+ }
77
+ /**
78
+ * True when no `ConversationDetail` row is on this fork and no fork names it as its parent.
79
+ * Records the refusal and returns false otherwise, and when either count cannot be read.
80
+ */
81
+ async hasNoRowsOrChildBranches() {
82
+ const id = EscapeSQLString(this.ID);
83
+ try {
84
+ const [rows, children] = await new RunView(this.RunViewProviderToUse).RunViews([
85
+ { EntityName: 'MJ: Conversation Details', ExtraFilter: `BranchID='${id}'`, ResultType: 'count_only' },
86
+ { EntityName: 'MJ: Conversation Branches', ExtraFilter: `ParentBranchID='${id}'`, ResultType: 'count_only' },
87
+ ], this.ContextCurrentUser);
88
+ if (!rows?.Success || !children?.Success) {
89
+ LogError(`MJConversationBranchEntityExtended: delete check failed for fork ${this.ID}: ${rows?.ErrorMessage ?? ''} ${children?.ErrorMessage ?? ''}`.trim());
90
+ this.recordDenied(FORK_DELETE_CHECK_FAILED_MESSAGE, 'delete');
91
+ return false;
92
+ }
93
+ if (rows.TotalRowCount > 0 || children.TotalRowCount > 0) {
94
+ this.recordDenied(FORK_DELETE_REFUSED_MESSAGE, 'delete');
95
+ return false;
96
+ }
97
+ return true;
98
+ }
99
+ catch (error) {
100
+ LogError(`MJConversationBranchEntityExtended: delete check failed for fork ${this.ID}: ${error instanceof Error ? error.message : String(error)}`);
101
+ this.recordDenied(FORK_DELETE_CHECK_FAILED_MESSAGE, 'delete');
102
+ return false;
103
+ }
104
+ }
105
+ recordDenied(message, resultType) {
106
+ const result = new BaseEntityResult();
107
+ result.Success = false;
108
+ result.Type = resultType;
109
+ result.Message = message;
110
+ result.StartedAt = new Date();
111
+ result.EndedAt = new Date();
112
+ this.RegisterResultHistoryEntry(result);
113
+ }
114
+ };
115
+ MJConversationBranchEntityExtended = __decorate([
116
+ RegisterClass(BaseEntity, 'MJ: Conversation Branches')
117
+ ], MJConversationBranchEntityExtended);
118
+ export { MJConversationBranchEntityExtended };
119
+ export function LoadMJConversationBranchEntityExtended() { }
120
+ //# sourceMappingURL=MJConversationBranchEntityExtended.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MJConversationBranchEntityExtended.js","sourceRoot":"","sources":["../../src/custom/MJConversationBranchEntityExtended.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,22 @@ import { MJConversationDetailEntity } from '../generated/entity_subclasses.js';
15
15
  * owner-only — even users with `Edit`-level grants on the conversation are
16
16
  * blocked from changing them, because the storage is a single value per
17
17
  * message (it represents the owner's evaluation, not per-grantee opinions).
18
+ *
19
+ * **Who a message is from** (A19, the rules for people's messages). For a `Role='User'` message, the owner and
20
+ * grantees alike:
21
+ * - post only as themselves: a new message naming another person's `UserID` is refused, and a grantee's message
22
+ * with no `UserID` gets the grantee's (an empty one reads as the conversation's owner). The owner's stays empty,
23
+ * since it already means them;
24
+ * - change who wrote it and what it says ({@link AUTHORED_FIELDS}) only when they wrote it, and never its role;
25
+ * - never move it to another conversation or fork ({@link PLACEMENT_FIELDS});
26
+ * - delete it only when they wrote it, or own the conversation.
27
+ *
28
+ * The system user is exempt from these authorship rules, since server code writes on people's behalf, but it still
29
+ * needs to own the conversation or hold a grant on it, as before: some server paths elevate an anonymous visitor to
30
+ * the system user (MJ#4791), and this gate keeps those paths out of other people's conversations, so no transaction
31
+ * variable may set the row or conversation it writes ({@link ACCESS_GATED_FIELDS}) for it either. The plan's "the
32
+ * system user may write any message" lands with A19's second half, which must design that boundary. Agent replies
33
+ * (`Role='AI'`/`'Error'`) keep today's rules until MJ's chat stops writing them from the browser.
18
34
  * MJ's permissions engine has no native primitive for per-field row-conditional
19
35
  * write rules, so this gate lives here rather than in `MJ: Entity Permissions`.
20
36
  *
@@ -28,11 +44,37 @@ export declare class MJConversationDetailEntityExtended extends MJConversationDe
28
44
  Delete(options?: EntityDeleteOptions): Promise<boolean>;
29
45
  private currentUserMayWrite;
30
46
  /**
31
- * Names of the rating/feedback fields that are dirty on this save.
32
- * Used by `currentUserMayWrite` to detect attempts by non-owners to
33
- * modify the owner-only fields and surface a specific denial message.
47
+ * Why the user may not write to this message's conversation at all, or `null` when they may. The owner may. Anyone
48
+ * else may not change the rating fields, which are the owner's alone, and needs an `Edit` or `Owner` grant. A
49
+ * conversation that does not load for the user is hidden from them when it exists (a row filter), and they may not
50
+ * write to it; when no one can find it, it is missing or being created in this same transaction, and the foreign
51
+ * key and the base save decide, as before.
52
+ */
53
+ private accessRefusal;
54
+ /**
55
+ * Why this write breaks the rules for people's messages (see the class doc), or `null` when it does not. On a new
56
+ * message from a grantee with no `UserID`, fills in the grantee's.
57
+ */
58
+ private authorshipRefusal;
59
+ /** A new person's message names its author as the poster, or names no one (filled in for a grantee). */
60
+ private postRefusal;
61
+ /**
62
+ * An existing person's message: only its author changes who wrote it and what it says, no one its role (to or
63
+ * from `User`), and no one moves it to another conversation or fork. A message with no `UserID` is the owner's.
64
+ */
65
+ private changeRefusal;
66
+ /** A person's message is deleted by its author or by the conversation's owner (whose it is with no `UserID`). */
67
+ private deleteRefusal;
68
+ /** Whether the conversation exists, read as the system user; false when there is no system user to ask. */
69
+ private conversationExists;
70
+ /**
71
+ * Whether a transaction variable of this message's group sets one of `fields` on it. The field is resolved the way
72
+ * `Set` resolves it at submit (trimmed, any case).
34
73
  */
35
- private dirtyRatingFieldNames;
74
+ private fieldSetByTransactionVariable;
75
+ private isFieldDirty;
76
+ /** A field's value as loaded, before this save's changes; `null` when it was empty or is not text. */
77
+ private originalFieldText;
36
78
  /**
37
79
  * Records WHY the write was refused as a NEW `ResultHistory` entry, so callers that read
38
80
  * `LatestResult.CompleteMessage` see the reason (issue #4791).
@@ -1 +1 @@
1
- {"version":3,"file":"MJConversationDetailEntityExtended.d.ts","sourceRoot":"","sources":["../../src/custom/MJConversationDetailEntityExtended.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,mBAAmB,EACnB,iBAAiB,EAGpB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACH,0BAA0B,EAG7B,MAAM,gCAAgC,CAAC;AAcxC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBACa,kCAAmC,SAAQ,0BAA0B;IAC/D,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAOnD,MAAM,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;YAOxD,mBAAmB;IA6EjC;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAS7B;;;;;;;;OAQG;IACH,OAAO,CAAC,YAAY;CASvB;AAED,6FAA6F;AAC7F,wBAAgB,sCAAsC,IAAI,IAAI,CAE7D"}
1
+ {"version":3,"file":"MJConversationDetailEntityExtended.d.ts","sourceRoot":"","sources":["../../src/custom/MJConversationDetailEntityExtended.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,mBAAmB,EAEnB,iBAAiB,EAKpB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,0BAA0B,EAAwB,MAAM,gCAAgC,CAAC;AA4ClG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBACa,kCAAmC,SAAQ,0BAA0B;IAC/D,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAYnD,MAAM,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;YAOxD,mBAAmB;IAwDjC;;;;;;OAMG;YACW,aAAa;IAoB3B;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAOzB,wGAAwG;IACxG,OAAO,CAAC,WAAW;IAgBnB;;;OAGG;IACH,OAAO,CAAC,aAAa;IA2BrB,iHAAiH;IACjH,OAAO,CAAC,aAAa;IASrB,2GAA2G;YAC7F,kBAAkB;IAahC;;;OAGG;IACH,OAAO,CAAC,6BAA6B;IAOrC,OAAO,CAAC,YAAY;IAIpB,sGAAsG;IACtG,OAAO,CAAC,iBAAiB;IAKzB;;;;;;;;OAQG;IACH,OAAO,CAAC,YAAY;CASvB;AAED,6FAA6F;AAC7F,wBAAgB,sCAAsC,IAAI,IAAI,CAE7D"}