@kumori/aurora-backend-handler 1.0.100 → 1.0.112

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.
@@ -1,4 +1,4 @@
1
- import { Channel, Service, Tenant, Usage } from "@kumori/aurora-interfaces";
1
+ import { Channel, Resource, Service, Tenant, Usage } from "@kumori/aurora-interfaces";
2
2
  import { Revision } from "@kumori/aurora-interfaces/interfaces/revision-interface";
3
3
  import { getTimestamp } from "../utils/utils";
4
4
 
@@ -98,7 +98,7 @@ const parseKeyPath = (key: string): { [entity: string]: string } => {
98
98
  return result;
99
99
  };
100
100
 
101
- const getDefaultUsage = (): Usage => ({
101
+ const getDefaultUsage = (eventData?: any): Usage => ({
102
102
  current: {
103
103
  cpu: 0,
104
104
  memory: 0,
@@ -108,12 +108,12 @@ const getDefaultUsage = (): Usage => ({
108
108
  persistentStorage: 0,
109
109
  },
110
110
  limit: {
111
- cpu: { max: 0, min: 0 },
112
- memory: { max: 0, min: 0 },
113
- storage: { max: 0, min: 0 },
114
- volatileStorage: { max: 0, min: 0 },
115
- nonReplicatedStorage: { max: 0, min: 0 },
116
- persistentStorage: { max: 0, min: 0 },
111
+ cpu: { max: eventData.vcpu || 0, min: eventData.vcpu || 0 },
112
+ memory: { max: eventData.ram || 0, min: eventData.ram || 0 },
113
+ storage: { max: (eventData.volatile_disk || 0) + (eventData.persistent_disk || 0) + (eventData.persistent_disk || 0), min: (eventData.volatile_disk || 0) + (eventData.persistent_disk || 0) + (eventData.persistent_disk || 0) },
114
+ volatileStorage: { max: eventData.volatile_disk || 0, min: eventData.volatile_disk || 0 },
115
+ nonReplicatedStorage: { max: eventData.persistent_disk || 0, min: eventData.persistent_disk || 0 },
116
+ persistentStorage: { max: eventData.persistent_disk || 0, min: eventData.persistent_disk || 0 },
117
117
  },
118
118
  cost: 0,
119
119
  });
@@ -123,6 +123,7 @@ const determineFinalStatusAndError = (
123
123
  eventData: any,
124
124
  pendingRevisionErrors: Array<{ service: string; revision: Revision }>,
125
125
  entityId: string,
126
+ currentRevision: Revision | undefined,
126
127
  ): {
127
128
  finalStatus: any;
128
129
  finalError: any;
@@ -131,37 +132,44 @@ const determineFinalStatusAndError = (
131
132
  const incomingStatus = eventData.status.state;
132
133
  const incomingTs = getTimestamp(incomingStatus.timestamp);
133
134
  const currentTs = getTimestamp(existingService?.status?.timestamp);
135
+ const revisionTs = currentRevision?.status ? getTimestamp(currentRevision.status.timestamp) : 0;
136
+
137
+ const bestCandidateTs = Math.max(incomingTs, revisionTs);
138
+ const isNewer = !existingService || bestCandidateTs > currentTs;
139
+
140
+ const removingStatus = {
141
+ code: "REMOVING_SERVICE",
142
+ message: `Service ${entityId} is being removed`,
143
+ args: [],
144
+ timestamp: new Date().toISOString(),
145
+ };
146
+
147
+ if (eventData.meta?.deleted || existingService?.status?.code === "REMOVING_SERVICE") {
148
+ return { finalStatus: removingStatus, finalError: undefined, pendingErrorIndex: -1 };
149
+ }
134
150
 
135
- const isNewer = !existingService || incomingTs > currentTs;
136
151
  let finalStatus = existingService?.status;
137
152
  let finalError = existingService?.error;
138
153
 
139
154
  if (isNewer) {
140
- finalStatus = incomingStatus;
141
- if (eventData.status.error) {
142
- finalError = eventData.status.error;
143
- } else {
144
- finalError = undefined;
145
- }
155
+ const revisionHasError = !!currentRevision?.errorCode;
156
+ finalStatus = (revisionHasError || (currentRevision?.status && revisionTs >= incomingTs))
157
+ ? currentRevision!.status
158
+ : incomingStatus;
159
+ finalError = eventData.status.error ?? undefined;
146
160
  }
147
161
 
148
162
  const pendingErrorIndex = pendingRevisionErrors.findIndex(
149
163
  (pending) => pending.service === entityId,
150
164
  );
151
-
152
165
  if (pendingErrorIndex !== -1) {
153
166
  const pendingError = pendingRevisionErrors[pendingErrorIndex];
154
- const pendingTs = getTimestamp(pendingError.revision.status.timestamp);
155
- const currentDecisionTs = getTimestamp(finalStatus?.timestamp);
156
-
157
- if (pendingTs > currentDecisionTs) {
158
- finalStatus = pendingError.revision.status;
159
- finalError = {
160
- code: pendingError.revision.errorCode || "",
161
- message: pendingError.revision.errorMsg || "",
162
- timestamp: pendingError.revision.status.timestamp || "",
163
- };
164
- }
167
+ finalStatus = pendingError.revision.status;
168
+ finalError = {
169
+ code: pendingError.revision.errorCode || "",
170
+ message: pendingError.revision.errorMsg || "",
171
+ timestamp: pendingError.revision.status.timestamp || "",
172
+ };
165
173
  }
166
174
 
167
175
  return { finalStatus, finalError, pendingErrorIndex };
@@ -180,16 +188,17 @@ export const handleServiceEvent = ({
180
188
  }: HandleServiceEventParams): HandleServiceEventResult => {
181
189
  const serviceFullKey = `${parentParts.tenant}/${entityId}`;
182
190
  const serviceTenantId = parentParts.tenant;
183
- const currentRevisionKey = `${serviceFullKey}-${eventData.status.revision}`;
191
+ const currentRevisionKey = `${eventData.id.parent.name}/service/${entityId}/revision/${eventData.spec.ctstamp || eventData.status.revision}`;
184
192
  const currentRevision = revisionsMap.get(currentRevisionKey);
185
193
 
186
194
  const projectLabel = eventData.meta?.labels?.project;
187
195
  const serviceRoles = roleMap.get(serviceFullKey) || [];
188
- const serviceUsage = currentRevision?.usage || getDefaultUsage();
196
+ const serviceUsage = currentRevision?.usage || getDefaultUsage(eventData);
197
+
189
198
 
190
199
  const serviceRevisions: Revision[] = [];
191
200
  revisionsMap.forEach((revision, key) => {
192
- if (key.startsWith(`${serviceFullKey}-`)) {
201
+ if (key.startsWith(`${eventData.id.parent.name}/service/${entityId}/revision/`)) {
193
202
  serviceRevisions.push(revision);
194
203
  }
195
204
  });
@@ -203,6 +212,7 @@ export const handleServiceEvent = ({
203
212
  eventData,
204
213
  pendingRevisionErrors,
205
214
  entityId,
215
+ currentRevision,
206
216
  );
207
217
 
208
218
  const updatedPendingRevisionErrors = [...pendingRevisionErrors];
@@ -458,6 +468,7 @@ export const handleServiceOperationError = ({
458
468
  export const mapChannelsFromApiData = (
459
469
  apiData: any,
460
470
  entityId: string,
471
+ existingResources?: Resource[],
461
472
  ): {
462
473
  serverChannels: Channel[];
463
474
  clientChannels: Channel[];
@@ -467,7 +478,6 @@ export const mapChannelsFromApiData = (
467
478
  const clientChannels: Channel[] = [];
468
479
  const duplexChannels: Channel[] = [];
469
480
  const inboundSuffix = "_inbound.inbound";
470
-
471
481
  const publicChannelNames = new Set<string>();
472
482
 
473
483
  const collectPublic = (section: any) => {
@@ -491,6 +501,12 @@ export const mapChannelsFromApiData = (
491
501
  Object.entries(section).forEach(
492
502
  ([channelName, channelData]: [string, any]) => {
493
503
  if (channelName.endsWith(inboundSuffix)) return;
504
+ const hasGatewayResource = existingResources?.some(
505
+ (r) =>
506
+ (r.type === "port" || r.type === "domain") &&
507
+ (r.name === `${channelName}_port` || r.name === `${channelName}_domain`),
508
+ ) ?? false;
509
+
494
510
  target.push({
495
511
  name: channelName,
496
512
  from: entityId,
@@ -498,7 +514,7 @@ export const mapChannelsFromApiData = (
498
514
  protocol: channelData.protocol as "http" | "tcp" | "https",
499
515
  port: channelData.port,
500
516
  portNum: channelData.port,
501
- isPublic: publicChannelNames.has(channelName),
517
+ isPublic: publicChannelNames.has(channelName) || hasGatewayResource,
502
518
  });
503
519
  },
504
520
  );
@@ -509,4 +525,4 @@ export const mapChannelsFromApiData = (
509
525
  mapChannels(apiData.duplex, duplexChannels);
510
526
 
511
527
  return { serverChannels, clientChannels, duplexChannels };
512
- };
528
+ };
@@ -1,4 +1,3 @@
1
-
2
1
  import { Tenant, tenantRole, User, UserData } from "@kumori/aurora-interfaces";
3
2
  import { parseKeyPath } from "../utils/utils";
4
3
 
@@ -98,10 +97,6 @@ export const handleTenantEvent: (
98
97
  ) {
99
98
  newTenant.npmRegistry.domain = Object.keys(eventData.spec.domains)[0];
100
99
  }
101
- } else {
102
- console.warn(
103
- `No se encontró información de registry en eventData para el tenant ${newTenant.name}`,
104
- );
105
100
  }
106
101
  return newTenant;
107
102
  };
@@ -1,10 +1,11 @@
1
- import { ClusterToken, Token, UserData } from "@kumori/aurora-interfaces";
1
+ import { ClusterToken, Notification, Token, UserData } from "@kumori/aurora-interfaces";
2
2
 
3
3
 
4
4
  interface HandleTokenEventParams {
5
5
  eventData: any;
6
6
  tokenMap: Map<string, Token>;
7
7
  userData: UserData;
8
+ deletedTokenIds: Set<string>;
8
9
  }
9
10
 
10
11
  interface HandleTokenEventResult {
@@ -24,10 +25,26 @@ export const handleTokenEvent = ({
24
25
  eventData,
25
26
  tokenMap,
26
27
  userData,
28
+ deletedTokenIds,
27
29
  }: HandleTokenEventParams): HandleTokenEventResult => {
28
30
  const tokenParentKind = eventData.id.parent?.kind;
29
31
  const tokenId = eventData.id.name;
30
32
 
33
+ if (tokenParentKind === "user" && deletedTokenIds.has(tokenId)) {
34
+ // A tombstoned token (already explicitly deleted by the user) must
35
+ // never be resurrected by a stale/out-of-order "token" event -
36
+ // e.g. one reflecting backend state that hasn't caught up with the
37
+ // deletion yet.
38
+ return {
39
+ isUserToken: false,
40
+ isClusterToken: false,
41
+ userToken: null,
42
+ clusterToken: null,
43
+ updatedUserTokens: userData.tokens,
44
+ tokenId,
45
+ };
46
+ }
47
+
31
48
  if (tokenParentKind === "user") {
32
49
  const tokenInMap = tokenMap.get(tokenId);
33
50
 
@@ -41,6 +58,10 @@ export const handleTokenEvent = ({
41
58
  : "",
42
59
  token: tokenInMap?.token || "",
43
60
  };
61
+ if (!tokenInMap) {
62
+ tokenMap.set(tokenId, newUserToken);
63
+ }
64
+
44
65
  const updatedUserTokens = [...userData.tokens];
45
66
  const existingTokenIndex = updatedUserTokens.findIndex(
46
67
  (t) => t.name === tokenId
@@ -76,7 +97,6 @@ export const handleTokenEvent = ({
76
97
  };
77
98
  }
78
99
  };
79
-
80
100
  interface HandleTokenOperationSuccessParams {
81
101
  responsePayload: any;
82
102
  }
@@ -104,4 +124,56 @@ export const handleTokenOperationSuccess = ({
104
124
  return {
105
125
  token: tokenWithData,
106
126
  };
127
+ };
128
+ interface HandleTokenDeletionParams {
129
+ tokenId: string;
130
+ tokenMap: Map<string, Token>;
131
+ userData: UserData;
132
+ deletedTokenIds: Set<string>;
133
+ }
134
+
135
+ interface HandleTokenDeletionResult {
136
+ removed: boolean;
137
+ updatedUserTokens: Token[];
138
+ notification: Notification | null;
139
+ }
140
+
141
+ export const handleTokenDeletion = ({
142
+ tokenId,
143
+ tokenMap,
144
+ userData,
145
+ deletedTokenIds,
146
+ }: HandleTokenDeletionParams): HandleTokenDeletionResult => {
147
+ deletedTokenIds.add(tokenId);
148
+ const tokenIndex = userData.tokens.findIndex((t) => t.name === tokenId);
149
+ if (tokenIndex === -1) {
150
+ return {
151
+ removed: false,
152
+ updatedUserTokens: userData.tokens,
153
+ notification: null,
154
+ };
155
+ }
156
+
157
+ const deletedToken = userData.tokens[tokenIndex];
158
+ const updatedUserTokens = [...userData.tokens];
159
+ updatedUserTokens.splice(tokenIndex, 1);
160
+ tokenMap.delete(tokenId);
161
+
162
+ const notification: Notification = {
163
+ type: "success",
164
+ subtype: "token-deleted",
165
+ date: Date.now().toString(),
166
+ status: "unread",
167
+ notificationType: "regular",
168
+ data: {
169
+ tokenName: deletedToken.name,
170
+ tenant: deletedToken.tenant,
171
+ },
172
+ };
173
+
174
+ return {
175
+ removed: true,
176
+ updatedUserTokens,
177
+ notification,
178
+ };
107
179
  };
@@ -1,6 +1,11 @@
1
- import { Notification, Plan, Tenant, UserData } from "@kumori/aurora-interfaces";
2
-
3
-
1
+ import {
2
+ Notification,
3
+ Plan,
4
+ Tenant,
5
+ UserData,
6
+ tenantRole,
7
+ UserStatus
8
+ } from "@kumori/aurora-interfaces";
4
9
 
5
10
  interface HandleUserEventParams {
6
11
  eventData: any;
@@ -38,17 +43,49 @@ export const handleUserEvent = ({
38
43
  if (!userTenants.includes(key)) {
39
44
  const tenantToDelete = tenantsMap.get(key);
40
45
  if (tenantToDelete) {
41
- const userRemovedNotification: Notification = {
42
- type: "info",
43
- subtype: "user-removed-from-tenant",
44
- date: Date.now().toString(),
45
- callToAction: false,
46
- status: "unread",
47
- data: {
48
- tenant: tenantToDelete.name,
49
- },
50
- };
51
- eventHelper.notification.publish.creation(userRemovedNotification);
46
+ const currentUserInTenant = tenantToDelete.users.find(
47
+ (u) => u.id === userId,
48
+ );
49
+ const isRejectedOrLeaving =
50
+ currentUserInTenant?.status === "rejected" ||
51
+ tenantToDelete.status === "leaving";
52
+
53
+ if (currentUserInTenant && !isRejectedOrLeaving) {
54
+ const onlyOneUser = tenantToDelete.users.length === 1;
55
+ const isSoloOwner =
56
+ onlyOneUser &&
57
+ currentUserInTenant.role === tenantRole.OWNER &&
58
+ currentUserInTenant.id === userId;
59
+
60
+ if (isSoloOwner) {
61
+ eventHelper.tenant.publish.deleted(tenantToDelete);
62
+ const tenantDeletedNotification: Notification = {
63
+ type: "success",
64
+ subtype: "tenant-deleted",
65
+ date: Date.now().toString(),
66
+ notificationType: "regular",
67
+ status: "unread",
68
+ data: {
69
+ tenant: tenantToDelete.name,
70
+ },
71
+ };
72
+ eventHelper.notification.publish.creation(
73
+ tenantDeletedNotification,
74
+ );
75
+ } else {
76
+ const userRemovedNotification: Notification = {
77
+ type: "info",
78
+ subtype: "user-removed-from-tenant",
79
+ date: Date.now().toString(),
80
+ notificationType: "regular",
81
+ status: "unread",
82
+ data: {
83
+ tenant: tenantToDelete.name,
84
+ },
85
+ };
86
+ eventHelper.notification.publish.creation(userRemovedNotification);
87
+ }
88
+ }
52
89
  deletedTenantKeys.push(key);
53
90
  }
54
91
  }
@@ -76,14 +113,14 @@ export const handleUserEvent = ({
76
113
  };
77
114
  userPlans.push(basicPlan);
78
115
  }
79
- }
116
+ },
80
117
  );
81
118
  }
82
119
  const tempProviders: Record<string, string> = eventData.spec.providers || {};
83
120
  const providers: { name: string; email: string; password?: string }[] = [];
84
121
 
85
122
  const existingProvidersMap = new Map(
86
- (currentUserData.provider || []).map((p) => [p.name, p])
123
+ (currentUserData.provider || []).map((p) => [p.name, p]),
87
124
  );
88
125
 
89
126
  for (const [providerName, providerId] of Object.entries(tempProviders)) {
@@ -100,6 +137,7 @@ export const handleUserEvent = ({
100
137
  }
101
138
  const userData: UserData = {
102
139
  id: userId,
140
+ created: eventData.meta?.created || currentUserData.created || "",
103
141
  name: eventData.spec.userinfo.name,
104
142
  surname: eventData.spec.userinfo.surname,
105
143
  provider: providers,
@@ -122,19 +160,21 @@ export const handleUserEvent = ({
122
160
  deletedTenantKeys,
123
161
  };
124
162
  };
125
- export const handleUserOperationError = (): UserData => {
126
- return {
127
- id: "",
128
- name: "",
129
- surname: "",
130
- provider: [],
131
- notificationsEnabled: "",
132
- organizations: [],
133
- clusterTokens: [],
134
- tokens: [],
135
- tenants: [],
136
- axebowPlan: "freemium",
137
- companyName: "",
138
- rol: "",
139
- };
163
+ export const handleUserDelete = (): UserData => {
164
+ return {
165
+ id: "",
166
+ created: "",
167
+ name: "",
168
+ surname: "",
169
+ provider: [],
170
+ notificationsEnabled: "",
171
+ organizations: [],
172
+ clusterTokens: [],
173
+ tokens: [],
174
+ tenants: [],
175
+ axebowPlan: "freemium",
176
+ companyName: "",
177
+ rol: "",
178
+ status: UserStatus.NOT_LOGGED,
179
+ };
140
180
  };
package/jest.config.ts CHANGED
@@ -19,6 +19,7 @@ const config: Config.InitialOptions = {
19
19
  moduleNameMapper: {
20
20
  '^(\\.{1,2}/.*)\\.js$': '$1',
21
21
  },
22
+ coverageProvider: 'v8',
22
23
  collectCoverage: true,
23
24
  collectCoverageFrom: [
24
25
  "**/backend-handler.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kumori/aurora-backend-handler",
3
- "version": "1.0.100",
3
+ "version": "1.0.112",
4
4
  "description": "backend handler",
5
5
  "main": "backend-handler.ts",
6
6
  "scripts": {
@@ -11,8 +11,8 @@
11
11
  "glob": "^11.0.0"
12
12
  },
13
13
  "dependencies": {
14
- "@kumori/aurora-interfaces": "^1.0.12",
15
- "@kumori/kumori-dsl-generator": "^1.0.4",
14
+ "@kumori/aurora-interfaces": "^1.0.31",
15
+ "@kumori/kumori-dsl-generator": "1.0.10",
16
16
  "@kumori/kumori-module-generator": "^1.1.6",
17
17
  "ts-node": "^10.9.2",
18
18
  "uuid": "^11.1.0",