• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

restorecommerce / resource-srv / 9427919672

08 Jun 2024 10:01AM UTC coverage: 71.315% (+0.2%) from 71.115%
9427919672

push

github

vanthome
chore: upgrade deps

67 of 90 branches covered (74.44%)

Branch coverage included in aggregate %.

833 of 1172 relevant lines covered (71.08%)

3.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.37
/src/worker.ts
1
import { Events, Topic, registerProtoMeta } from '@restorecommerce/kafka-client';
1✔
2
import { GraphResourcesServiceBase, ResourcesAPIBase } from '@restorecommerce/resource-base-interface';
1✔
3
import { ACSAuthZ, initAuthZ, initializeCache } from '@restorecommerce/acs-client';
1✔
4
import { ResourceCommandInterface } from './commandInterface.js';
1✔
5
import {
1✔
6
  database,
1✔
7
  GraphDatabaseProvider,
1✔
8
  buildReflectionService,
1✔
9
  CommandInterface,
1✔
10
  OffsetStore,
1✔
11
  Server,
1✔
12
  Health
1✔
13
} from '@restorecommerce/chassis-srv';
1✔
14
import { Logger } from 'winston';
1✔
15
import { createLogger } from '@restorecommerce/logger';
1✔
16
import { createServiceConfig } from '@restorecommerce/service-config';
1✔
17
import { createClient, RedisClientType } from 'redis';
1✔
18
import {
1✔
19
  DeepPartial,
1✔
20
  protoMetadata as commandMeta,
1✔
21
  CommandServiceDefinition as command
1✔
22
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/command.js';
1✔
23
import {
1✔
24
  protoMetadata as addressMeta,
1✔
25
  AddressServiceDefinition as address
1✔
26
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/address.js';
1✔
27
import {
1✔
28
  protoMetadata as contactPointTypeMeta,
1✔
29
  ContactPointTypeServiceDefinition as contact_point_type
1✔
30
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/contact_point_type.js';
1✔
31
import {
1✔
32
  protoMetadata as countryMeta,
1✔
33
  CountryServiceDefinition as country
1✔
34
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/country.js';
1✔
35
import {
1✔
36
  protoMetadata as contactPointMeta,
1✔
37
  ContactPointServiceDefinition as contact_point
1✔
38
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/contact_point.js';
1✔
39
import {
1✔
40
  protoMetadata as credentialMeta,
1✔
41
  CredentialServiceDefinition as credential
1✔
42
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/credential.js';
1✔
43
import {
1✔
44
  protoMetadata as localeMeta,
1✔
45
  LocaleServiceDefinition as locale
1✔
46
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/locale.js';
1✔
47
import {
1✔
48
  protoMetadata as locationMeta,
1✔
49
  LocationServiceDefinition as location
1✔
50
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/location.js';
1✔
51
import {
1✔
52
  protoMetadata as organizationMeta,
1✔
53
  OrganizationServiceDefinition as organization
1✔
54
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/organization.js';
1✔
55
import {
1✔
56
  protoMetadata as taxMeta,
1✔
57
  TaxServiceDefinition as tax
1✔
58
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/tax.js';
1✔
59
import {
1✔
60
  protoMetadata as taxTypeMeta,
1✔
61
  TaxTypeServiceDefinition as tax_type
1✔
62
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/tax_type.js';
1✔
63
import {
1✔
64
  protoMetadata as timezoneMeta,
1✔
65
  TimezoneServiceDefinition as timezone
1✔
66
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/timezone.js';
1✔
67
import {
1✔
68
  protoMetadata as customerMeta,
1✔
69
  CustomerServiceDefinition as customer
1✔
70
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/customer.js';
1✔
71
import {
1✔
72
  protoMetadata as shopMeta,
1✔
73
  ShopServiceDefinition as shop
1✔
74
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/shop.js';
1✔
75
import {
1✔
76
  protoMetadata as unitCodeMeta,
1✔
77
  UnitCodeServiceDefinition as unit_code
1✔
78
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/unit_code.js';
1✔
79
import {
1✔
80
  protoMetadata as templateMeta,
1✔
81
  TemplateServiceDefinition as template
1✔
82
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/template.js';
1✔
83
import {
1✔
84
  protoMetadata as notificationMeta,
1✔
85
  NotificationServiceDefinition as notification
1✔
86
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/notification.js';
1✔
87
import {
1✔
88
  protoMetadata as notificationChannelMeta,
1✔
89
  NotificationChannelServiceDefinition as notification_channel
1✔
90
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/notification_channel.js';
1✔
91
import {
1✔
92
  CommandInterfaceServiceDefinition as CommandInterfaceServiceDefinition,
1✔
93
  protoMetadata as commandInterfaceMeta
1✔
94
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/commandinterface.js';
1✔
95
import {
1✔
96
  protoMetadata as reflectionMeta
1✔
97
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/grpc/reflection/v1alpha/reflection.js';
1✔
98
import { ServerReflectionService } from 'nice-grpc-server-reflection';
1✔
99
import { HealthDefinition } from '@restorecommerce/rc-grpc-clients/dist/generated-server/grpc/health/v1/health.js';
1✔
100
import {
1✔
101
  GraphServiceDefinition as GraphServiceDefinition,
1✔
102
  protoMetadata as graphMeta,
1✔
103
  GraphServiceClient
1✔
104
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/graph.js';
1✔
105
import { BindConfig } from '@restorecommerce/chassis-srv/lib/microservice/transport/provider/grpc/index.js';
1✔
106
import { protoMetadata as hierarchicalScopesMeta } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/auth.js';
1✔
107
import { UserServiceClient } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/user.js';
1✔
108
import { ResourceService } from './service.js';
1✔
109
import { getUserServiceClient, getGraphServiceClient, createHRScope } from './utils.js';
1✔
110

1✔
111
const COMMANDEVENTS = [
1✔
112
  'restoreCommand',
1✔
113
  'healthCheckCommand',
1✔
114
  'resetCommand',
1✔
115
  'versionCommand',
1✔
116
  'configUpdateCommand',
1✔
117
  'setApiKeyCommand',
1✔
118
  'flushCacheCommand'
1✔
119
];
1✔
120
const HIERARCHICAL_SCOPE_REQUEST_EVENT = 'hierarchicalScopesRequest';
1✔
121

1✔
122
registerProtoMeta(
1✔
123
  commandMeta,
1✔
124
  addressMeta,
1✔
125
  contactPointTypeMeta,
1✔
126
  countryMeta,
1✔
127
  contactPointMeta,
1✔
128
  credentialMeta,
1✔
129
  localeMeta,
1✔
130
  locationMeta,
1✔
131
  organizationMeta,
1✔
132
  taxMeta,
1✔
133
  taxTypeMeta,
1✔
134
  timezoneMeta,
1✔
135
  customerMeta,
1✔
136
  shopMeta,
1✔
137
  templateMeta,
1✔
138
  commandInterfaceMeta,
1✔
139
  reflectionMeta,
1✔
140
  graphMeta,
1✔
141
  unitCodeMeta,
1✔
142
  notificationMeta,
1✔
143
  notificationChannelMeta,
1✔
144
  hierarchicalScopesMeta
1✔
145
);
1✔
146

1✔
147
const ServiceDefinitions: any = [
1✔
148
  command,
1✔
149
  address,
1✔
150
  contact_point_type,
1✔
151
  country,
1✔
152
  contact_point,
1✔
153
  credential,
1✔
154
  locale,
1✔
155
  location,
1✔
156
  organization,
1✔
157
  tax,
1✔
158
  tax_type,
1✔
159
  timezone,
1✔
160
  customer,
1✔
161
  shop,
1✔
162
  unit_code,
1✔
163
  template,
1✔
164
  notification,
1✔
165
  notification_channel,
1✔
166
];
1✔
167

1✔
168
export class Worker {
1✔
169
  server?: Server;
2✔
170
  events?: Events;
2✔
171
  logger?: Logger;
2✔
172
  redisClient: any;
2✔
173
  offsetStore?: OffsetStore;
2✔
174
  cis?: CommandInterface;
2✔
175
  services?: any[];
2✔
176
  idsClient?: UserServiceClient;
2✔
177
  graphClient?: GraphServiceClient;
2✔
178

2✔
179
  async start(cfg?: any, resourcesServiceEventListener?: Function) {
2✔
180
    // Load config
2✔
181
    if (!cfg) {
2!
182
      cfg = createServiceConfig(process.cwd());
×
183
    }
×
184
    const resources = cfg.get('resources');
2✔
185
    if (!resources) {
2!
186
      throw new Error('config field resources does not exist');
×
187
    }
×
188

2✔
189
    // Generate a config for each resource
2✔
190
    const kafkaCfg = cfg.get('events:kafka');
2✔
191
    const grpcConfig = cfg.get('server:transports:0');
2✔
192

2✔
193
    const validResourceTopicNames: string[] = [];
2✔
194

2✔
195
    const eventTypes = ['Created', 'Read', 'Modified', 'Deleted'];
2✔
196
    for (let resourceType in resources) {
2✔
197
      const resourceCfg = resources[resourceType];
2✔
198
      const resourcesServiceNamePrefix = resourceCfg.resourcesServiceNamePrefix;
2✔
199
      for (let resource of resourceCfg.resources) {
2✔
200
        let resourceObjectName = resource.charAt(0).toUpperCase() + resource.substr(1);
6✔
201

6✔
202
        if (resource.indexOf('_') != -1) {
6✔
203
          const names = resourceObjectName.split('_');
2✔
204
          resourceObjectName = '';
2✔
205

2✔
206
          for (let name of names) {
2✔
207
            resourceObjectName += name.charAt(0).toUpperCase() + name.substr(1);
4✔
208
          }
4✔
209
        }
2✔
210

6✔
211
        for (let event of eventTypes) {
6✔
212
          kafkaCfg[`${resource}${event}`] = {
24✔
213
            messageObject: `${resourcesServiceNamePrefix}${resource}.${resourceObjectName}`
24✔
214
          };
24✔
215

24✔
216
          const topicName = `${resourcesServiceNamePrefix}${resource}s.resource`;
24✔
217
          const topicLabel = `${resource}.resource`;
24✔
218
          kafkaCfg.topics[topicLabel] = {
24✔
219
            topic: topicName,
24✔
220
          };
24✔
221
          validResourceTopicNames.push(topicName);
24✔
222
        }
24✔
223
      }
6✔
224
    }
2✔
225
    cfg.set('events:kafka', kafkaCfg);
2✔
226

2✔
227
    const loggerCfg = cfg.get('logger');
2✔
228
    const logger = createLogger(loggerCfg);
2✔
229
    this.logger = logger;
2✔
230
    const server = new Server(cfg.get('server'), logger);
2✔
231
    const db = await database.get(cfg.get('database:arango'),
2✔
232
      logger, cfg.get('graph:graphName'), cfg.get('graph:edgeDefinitions')) as GraphDatabaseProvider;
2✔
233
    const events = new Events(cfg.get('events:kafka'), logger);
2✔
234

2✔
235
    await events.start();
2✔
236
    this.offsetStore = new OffsetStore(events, cfg, logger);
2✔
237
    let redisClient: RedisClientType<any, any> | undefined;
2✔
238
    if (cfg.get('redis')) {
2✔
239
      const redisConfig = cfg.get('redis');
2✔
240
      redisConfig.database = cfg.get('redis:db-indexes:db-resourcesCounter');
2✔
241
      redisClient = createClient(redisConfig);
2✔
242
      redisClient.on('error', (err) => logger.error('Redis Client Error', err));
2✔
243
      await redisClient.connect();
2✔
244
    }
2✔
245
    else {
×
246
      redisClient = undefined;
×
247
    }
×
248
    const fieldGeneratorConfig: any = cfg.get('fieldHandlers:fieldGenerators');
2✔
249
    const bufferHandlerConfig: any = cfg.get('fieldHandlers:bufferFields');
2✔
250
    const requiredFieldsConfig: any = cfg.get('fieldHandlers:requiredFields');
2✔
251

2✔
252
    // Enable events firing for resource api using config
2✔
253
    const isEventsEnabled = (cfg.get('events:enableCRUDEvents') == 'true');
2✔
254
    const graphCfg = cfg.get('graph');
2✔
255

2✔
256
    this.services = [];
2✔
257
    const authZ = await initAuthZ(cfg) as ACSAuthZ;
2✔
258
    // init Redis Client for subject index
2✔
259
    const redisConfig = cfg.get('redis');
2✔
260
    redisConfig.database = cfg.get('redis:db-indexes:db-subject');
2✔
261
    const redisClientSubject: RedisClientType = createClient(redisConfig);
2✔
262
    await redisClientSubject.on('error', (err) => logger.error('Redis Client Error', err));
2✔
263
    await redisClientSubject.connect();
2✔
264
    for (let resourceType in resources) {
2✔
265
      const resourceCfg = resources[resourceType];
2✔
266
      const resourcesServiceConfigPrefix = resourceCfg.resourcesServiceConfigPrefix;
2✔
267
      const resourcesServiceNamePrefix = resourceCfg.resourcesServiceNamePrefix;
2✔
268

2✔
269
      for (let resourceName of resourceCfg.resources) {
2✔
270
        let resourceFieldConfig: any = {};
6✔
271
        if (fieldGeneratorConfig && (resourceName in fieldGeneratorConfig)) {
6!
272
          resourceFieldConfig['strategies'] = fieldGeneratorConfig[resourceName];
×
273
          logger.info('Setting up field generators on Redis...');
×
274
          resourceFieldConfig['redisClient'] = redisClient;
×
275
        }
×
276
        const collectionName = `${resourceName}s`;
6✔
277
        // bufferFields handler
6✔
278
        if (bufferHandlerConfig && (collectionName in bufferHandlerConfig)) {
6✔
279
          resourceFieldConfig['bufferFields'] = bufferHandlerConfig[collectionName];
2✔
280
        }
2✔
281
        // dateTimeStampFields handler
6✔
282
        if (cfg.get('fieldHandlers:timeStampFields')) {
6✔
283
          resourceFieldConfig['timeStampFields'] = [];
6✔
284
          for (let timeStampFiledConfig of cfg.get('fieldHandlers:timeStampFields')) {
6✔
285
            if (timeStampFiledConfig.entities.includes(collectionName)) {
6✔
286
              resourceFieldConfig['timeStampFields'].push(...timeStampFiledConfig.fields);
6✔
287
            }
6✔
288
          }
6✔
289
        }
6✔
290
        // requiredFields handler
6✔
291
        if (requiredFieldsConfig && (collectionName in requiredFieldsConfig)) {
6!
292
          resourceFieldConfig['requiredFields'] = requiredFieldsConfig;
×
293
        }
×
294
        logger.info(`Setting up ${resourceName} resource service`);
6✔
295

6✔
296
        let edgeCfg;
6✔
297
        let graphName;
6✔
298
        if (graphCfg && graphCfg.vertices) {
6✔
299
          const collectionName = `${resourceName}s`;
6✔
300
          edgeCfg = graphCfg.vertices[collectionName];
6✔
301
        }
6✔
302
        if (graphCfg) {
6✔
303
          graphName = graphCfg.graphName;
6✔
304
        }
6✔
305
        const resourceAPI = new ResourcesAPIBase(db, `${resourceName}s`,
6✔
306
          resourceFieldConfig, edgeCfg, graphName);
6✔
307
        const resourceEvents = await events.topic(`${resourcesServiceNamePrefix}${resourceName}s.resource`);
6✔
308
        // TODO provide typing on ResourceService<T, M>
6✔
309
        this.services[resourceName] = new ResourceService(resourceName,
6✔
310
          resourceEvents, cfg, logger, resourceAPI, isEventsEnabled, authZ, redisClientSubject);
6✔
311
        const resourceServiceDefinition = ServiceDefinitions.filter((obj: any) => obj.fullName.split('.')[2] === resourceName);
6✔
312
        // todo add bindConfig typing
6✔
313
        await server.bind(`${resourcesServiceConfigPrefix}${resourceName}-srv`, {
6✔
314
          service: resourceServiceDefinition[0],
6✔
315
          implementation: this.services[resourceName]
6✔
316
        } as BindConfig<any>);
6✔
317
      }
6✔
318
    }
2✔
319

2✔
320
    // init ACS cache
2✔
321
    await initializeCache();
2✔
322

2✔
323
    // Add CommandInterfaceService
2✔
324
    const cis: ResourceCommandInterface = new ResourceCommandInterface(server, cfg, logger, events, redisClientSubject);
2✔
325
    const cisName = cfg.get('command-interface:name');
2✔
326
    await server.bind(cisName, {
2✔
327
      service: CommandInterfaceServiceDefinition,
2✔
328
      implementation: cis
2✔
329
    } as BindConfig<CommandInterfaceServiceDefinition>);
2✔
330

2✔
331
    const hrTopicName = kafkaCfg?.topics?.user?.topic;
2✔
332
    const hrTopic = await events.topic(hrTopicName);
2✔
333
    this.idsClient = await getUserServiceClient();
2✔
334
    this.graphClient = await getGraphServiceClient();
2✔
335

2✔
336
    if (!resourcesServiceEventListener) {
2✔
337
      resourcesServiceEventListener = async (
2✔
338
        msg: any,
×
339
        context: any,
×
340
        config: any,
×
341
        eventName: string
×
342
      ): Promise<any> => {
×
343
        if (COMMANDEVENTS.indexOf(eventName) > -1) {
×
344
          await cis.command(msg, context).catch(
×
345
            err => logger.error('Error while executing command', err)
×
346
          );
×
347
        } else if (eventName === HIERARCHICAL_SCOPE_REQUEST_EVENT) {
×
348
          const token = msg.token?.split(':')?.[0] as string;
×
349
          const user = token ? await this.idsClient?.findByToken({ token }) : undefined;
×
350
          if (!user?.payload?.id) {
×
351
            this.logger?.debug('Subject could not be resolved for token');
×
352
          }
×
353
          const subject = user?.payload?.id ? await createHRScope(user, token, this.graphClient!, null, cfg, this.logger) : undefined;
×
354
          if (hrTopic) {
×
355
            // emit response with same messag id on same topic
×
356
            this.logger?.info(`Hierarchical scopes are created for subject ${user?.payload?.id}`);
×
357
            await hrTopic.emit('hierarchicalScopesResponse', {
×
358
              subject_id: user?.payload?.id,
×
359
              token: msg.token,
×
360
              hierarchical_scopes: subject?.hierarchical_scopes
×
361
            });
×
362
          }
×
363
        }
×
364
      };
2✔
365
    }
2✔
366

2✔
367
    const topicTypes = Object.keys(kafkaCfg.topics);
2✔
368
    for (let topicType of topicTypes) {
2✔
369
      const topicName = kafkaCfg.topics[topicType].topic;
14✔
370
      const topic: Topic = await events.topic(topicName);
14✔
371
      const offSetValue = await this.offsetStore.getOffset(topicName);
14✔
372
      logger.info('subscribing to topic with offset value', topicName, offSetValue);
14✔
373
      if (kafkaCfg.topics[topicType].events) {
14✔
374
        const eventNames = kafkaCfg.topics[topicType].events;
8✔
375
        for (let eventName of eventNames) {
8✔
376
          await topic.on(eventName, resourcesServiceEventListener, { startingOffset: offSetValue });
2✔
377
        }
2✔
378
      }
8✔
379
    }
14✔
380

2✔
381
    // Add reflection service
2✔
382
    const reflectionService = buildReflectionService([
2✔
383
      { descriptor: commandMeta.fileDescriptor as any },
2✔
384
      { descriptor: addressMeta.fileDescriptor },
2✔
385
      { descriptor: contactPointTypeMeta.fileDescriptor },
2✔
386
      { descriptor: countryMeta.fileDescriptor },
2✔
387
      { descriptor: credentialMeta.fileDescriptor },
2✔
388
      { descriptor: localeMeta.fileDescriptor },
2✔
389
      { descriptor: locationMeta.fileDescriptor },
2✔
390
      { descriptor: organizationMeta.fileDescriptor },
2✔
391
      { descriptor: taxMeta.fileDescriptor },
2✔
392
      { descriptor: taxTypeMeta.fileDescriptor },
2✔
393
      { descriptor: timezoneMeta.fileDescriptor },
2✔
394
      { descriptor: customerMeta.fileDescriptor },
2✔
395
      { descriptor: shopMeta.fileDescriptor },
2✔
396
      { descriptor: commandInterfaceMeta.fileDescriptor },
2✔
397
      { descriptor: unitCodeMeta.fileDescriptor }
2✔
398
    ]);
2✔
399
    await server.bind('reflection', {
2✔
400
      service: ServerReflectionService,
2✔
401
      implementation: reflectionService
2✔
402
    });
2✔
403

2✔
404
    // graph Service
2✔
405
    const graphAPIService = new GraphResourcesServiceBase(db, cfg.get('fieldHandlers:bufferFields'));
2✔
406
    await server.bind('graph', {
2✔
407
      implementation: graphAPIService,
2✔
408
      service: GraphServiceDefinition
2✔
409
    } as BindConfig<GraphServiceDefinition>);
2✔
410

2✔
411
    // health Service
2✔
412
    await server.bind('grpc-health-v1', {
2✔
413
      service: HealthDefinition,
2✔
414
      implementation: new Health(cis, {
2✔
415
        logger,
2✔
416
        cfg
2✔
417
      })
2✔
418
    } as BindConfig<HealthDefinition>);
2✔
419

2✔
420
    // Start server
2✔
421
    await server.start();
2✔
422
    logger.info('Server Started Successfully');
2✔
423
    this.events = events;
2✔
424
    this.server = server;
2✔
425
    this.logger = logger;
2✔
426
    this.cis = cis;
2✔
427

2✔
428
    if (redisClient) {
2✔
429
      this.redisClient = redisClient;
2✔
430
    }
2✔
431
  }
2✔
432

2✔
433
  async stop() {
2✔
434
    this.logger?.info('Shutting down');
2✔
435
    await this.server?.stop();
2✔
436
    await this.events?.stop();
2✔
437
    await this.offsetStore?.stop();
2✔
438
  }
2✔
439
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc