@nestjs/common#OnModuleInit TypeScript Examples

The following examples show how to use @nestjs/common#OnModuleInit. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: health.module.ts    From relate with GNU General Public License v3.0 6 votes vote down vote up
@Module({
    exports: [HealthService],
    imports: [ConfigModule, SystemModule],
    providers: [HealthService],
})
export class HealthModule implements OnModuleInit {
    constructor(
        @Inject(HttpAdapterHost) private readonly httpAdapterHost: HttpAdapterHost,
        @Inject(HealthService) private readonly loader: HealthService,
    ) {}

    async onModuleInit(): Promise<void> {
        if (!this.httpAdapterHost) {
            return;
        }

        const {httpAdapter} = this.httpAdapterHost;

        await this.loader.register(httpAdapter);
    }
}
Example #2
Source File: files.module.ts    From relate with GNU General Public License v3.0 6 votes vote down vote up
@Module({
    exports: [FilesService],
    imports: [SystemModule],
    providers: [FilesService],
})
export class FilesModule implements OnModuleInit {
    constructor(
        @Inject(HttpAdapterHost) private readonly httpAdapterHost: HttpAdapterHost,
        @Inject(FilesService) private readonly loader: FilesService,
    ) {}

    onModuleInit(): void {
        if (!this.httpAdapterHost) {
            return;
        }

        const {httpAdapter} = this.httpAdapterHost;

        this.loader.register(httpAdapter);
    }
}
Example #3
Source File: post-data-initializer.service.ts    From nestjs-rest-sample with GNU General Public License v3.0 6 votes vote down vote up
@Injectable()
export class PostDataInitializerService
  implements OnModuleInit {
  private data: CreatePostDto[] = [
    {
      title: 'Generate a NestJS project',
      content: 'content',
    },
    {
      title: 'Create CRUD RESTful APIs',
      content: 'content',
    },
    {
      title: 'Connect to MongoDB',
      content: 'content',
    },
  ];

  constructor(
    @Inject(POST_MODEL) private postModel: Model<Post>,
    @Inject(COMMENT_MODEL) private commentModel: Model<Comment>,
  ) { }

  async onModuleInit(): Promise<void> {
    console.log('(PostModule) is initialized...');
    await this.postModel.deleteMany({});
    await this.commentModel.deleteMany({});
    await this.postModel.insertMany(this.data).then((r) => console.log(r));
  }
}
Example #4
Source File: user-data-initializer.service.ts    From nestjs-rest-sample with GNU General Public License v3.0 6 votes vote down vote up
@Injectable()
export class UserDataInitializerService
  implements OnModuleInit {
  constructor(@Inject(USER_MODEL) private userModel: Model<User>) { }

  async onModuleInit(): Promise<void> {
    console.log('(UserModule) is initialized...');
    await this.userModel.deleteMany({});
    const user = {
      username: 'hantsy',
      password: 'password',
      email: '[email protected]',
      roles: [RoleType.USER],
    };

    const admin = {
      username: 'admin',
      password: 'password',
      email: '[email protected]',
      roles: [RoleType.ADMIN],
    };
    await Promise.all(
      [
        this.userModel.create(user),
        this.userModel.create(admin)
      ]
    ).then(
      data => console.log(data)
    );
  }

}
Example #5
Source File: extension.module.ts    From relate with GNU General Public License v3.0 6 votes vote down vote up
@Module({
    imports: [SystemModule],
    providers: [ExtensionResolver, AppsService],
})
export class ExtensionModule implements OnModuleInit {
    constructor(
        @Inject(HttpAdapterHost) private readonly httpAdapterHost: HttpAdapterHost,
        @Inject(AppsService) private readonly loader: AppsService,
    ) {}

    async onModuleInit(): Promise<void> {
        if (!this.httpAdapterHost) {
            return;
        }

        const {httpAdapter} = this.httpAdapterHost;

        await this.loader.register(httpAdapter);
    }
}
Example #6
Source File: image.resolver.ts    From nestjs-mercurius with MIT License 6 votes vote down vote up
@Resolver()
export class ImageResolver implements OnModuleInit {
  private readonly uploadDir = path.join(process.cwd(), 'uploads');

  async onModuleInit() {
    if (!fs.existsSync(this.uploadDir)) {
      await fs.promises.mkdir(this.uploadDir);
    }
  }

  @Mutation(() => Boolean)
  async uploadImage(
    @Args({ name: 'file', type: () => GraphQLUpload })
    image: Promise<FileUpload>,
  ) {
    const { filename, createReadStream } = await image;
    const rs = createReadStream();
    const ws = fs.createWriteStream(path.join(this.uploadDir, filename));
    await pipeline(rs, ws);

    return true;
  }
}
Example #7
Source File: auth.module.ts    From relate with GNU General Public License v3.0 6 votes vote down vote up
@Module({
    exports: [AuthService],
    imports: [SystemModule],
    providers: [AuthService],
})
export class AuthModule implements OnModuleInit, NestModule {
    constructor(
        @Inject(HttpAdapterHost) private readonly httpAdapterHost: HttpAdapterHost,
        @Inject(AuthService) private readonly loader: AuthService,
    ) {}

    configure(consumer: MiddlewareConsumer) {
        consumer.apply(cookieParser()).forRoutes('*');

        consumer
            .apply(ApiTokenMiddleware, AuthTokenMiddleware)
            .exclude(
                {
                    path: '/graphql',
                    method: RequestMethod.GET,
                },
                {
                    path: '/api-docs',
                    method: RequestMethod.GET,
                },
            )
            .forRoutes('*');
    }

    onModuleInit(): void {
        if (!this.httpAdapterHost) {
            return;
        }

        const {httpAdapter} = this.httpAdapterHost;

        this.loader.register(httpAdapter);
    }
}
Example #8
Source File: DesktopAPI.ts    From rewind with MIT License 5 votes vote down vote up
@Module({
    imports: [EventEmitterModule.forRoot()],
    controllers: [
      LocalReplayController,
      SkinController,
      LocalBlueprintController,
      NormalStatusController,
      DesktopConfigController,
    ],
    providers: [
      { provide: OSU_FOLDER, useValue: osuFolder },
      { provide: OSU_SONGS_FOLDER, useValue: songsFolder },
      { provide: REWIND_CFG_PATH, useValue: rewindCfgPath },
      { provide: SKIN_NAME_RESOLVER_CONFIG, useValue: skinNameResolverConfig },
      SkinNameResolver,
      SkinService,
      EventsGateway,
      ReplayWatcher,
      LocalReplayService,
      LocalBlueprintService,
      OsuDBDao,
      DesktopConfigService,
    ],
  })
  class RewindDesktopModule implements OnModuleInit {
    constructor(private moduleRef: ModuleRef) {}

    async onModuleInit(): Promise<void> {
      const [osuFolder, replayWatcher, localBlueprintService] = await Promise.all([
        this.moduleRef.resolve(OSU_FOLDER),
        this.moduleRef.resolve(ReplayWatcher),
        this.moduleRef.resolve(LocalBlueprintService),
      ]);

      const replaysFolder = join(osuFolder, "Replays");
      replayWatcher.watchForReplays(replaysFolder);

      localBlueprintService
        .getAllBlueprints()
        .then((blueprints) => Logger.log(`Loaded all ${Object.keys(blueprints).length} blueprints.`));
      // TODO: Emit and then set the status to booted
      Logger.log(`RewindDesktopModule onModuleInit finished with settings: ${JSON.stringify(settings)}`);
    }
  }
Example #9
Source File: DesktopAPI.ts    From rewind with MIT License 5 votes vote down vote up
/**
 * The usual bootstrap happens with the concrete knowledge of the osu! folder. Only at the first start up of the
 * application we will have to refer to boot differently.
 */
async function normalBootstrap(settings: {
  osuFolder: string;
  songsFolder: string;
  userDataPath: string;
  appResourcesPath: string;
  logDirectory: string;
}) {
  const { osuFolder, userDataPath, appResourcesPath, logDirectory, songsFolder } = settings;
  // Find out osu! folder through settings
  const rewindCfgPath = getRewindCfgPath(userDataPath);
  const skinNameResolverConfig: SkinNameResolverConfig = [
    { prefix: "osu", path: join(osuFolder, "Skins") },
    { prefix: "rewind", path: join(appResourcesPath, "Skins") },
  ];

  @Module({
    imports: [EventEmitterModule.forRoot()],
    controllers: [
      LocalReplayController,
      SkinController,
      LocalBlueprintController,
      NormalStatusController,
      DesktopConfigController,
    ],
    providers: [
      { provide: OSU_FOLDER, useValue: osuFolder },
      { provide: OSU_SONGS_FOLDER, useValue: songsFolder },
      { provide: REWIND_CFG_PATH, useValue: rewindCfgPath },
      { provide: SKIN_NAME_RESOLVER_CONFIG, useValue: skinNameResolverConfig },
      SkinNameResolver,
      SkinService,
      EventsGateway,
      ReplayWatcher,
      LocalReplayService,
      LocalBlueprintService,
      OsuDBDao,
      DesktopConfigService,
    ],
  })
  class RewindDesktopModule implements OnModuleInit {
    constructor(private moduleRef: ModuleRef) {}

    async onModuleInit(): Promise<void> {
      const [osuFolder, replayWatcher, localBlueprintService] = await Promise.all([
        this.moduleRef.resolve(OSU_FOLDER),
        this.moduleRef.resolve(ReplayWatcher),
        this.moduleRef.resolve(LocalBlueprintService),
      ]);

      const replaysFolder = join(osuFolder, "Replays");
      replayWatcher.watchForReplays(replaysFolder);

      localBlueprintService
        .getAllBlueprints()
        .then((blueprints) => Logger.log(`Loaded all ${Object.keys(blueprints).length} blueprints.`));
      // TODO: Emit and then set the status to booted
      Logger.log(`RewindDesktopModule onModuleInit finished with settings: ${JSON.stringify(settings)}`);
    }
  }

  const app = await NestFactory.create<NestExpressApplication>(RewindDesktopModule, {
    logger: createLogger(logDirectory),
  });

  app.setGlobalPrefix(globalPrefix);
  app.enableCors();

  // So that "rewind" skins are also accessible
  skinNameResolverConfig.forEach((config) => {
    app.useStaticAssets(config.path, { prefix: `/static/skins/${config.prefix}` });
  });
  app.useStaticAssets(songsFolder, { prefix: "/static/songs" });
  // app.useLogger();

  await app.listen(port, listenCallback);
}
Example #10
Source File: bot.service.ts    From trading-bot with MIT License 5 votes vote down vote up
@Injectable()
export class BotService implements OnModuleInit {
  private readonly logger = new Logger(BotService.name)
  private readonly meanReversion = new MeanReversionService({
    keyId: this.configService.get<EnvironmentalVariables>(
      EnvironmentalVariables.ALPACA_API_KEY,
    ),
    secretKey: this.configService.get<EnvironmentalVariables>(
      EnvironmentalVariables.ALPACA_SECRET_KEY,
    ),
    paper: true,
  })

  private readonly longShort = new LongShortService({
    keyId: this.configService.get<EnvironmentalVariables>(
      EnvironmentalVariables.ALPACA_API_KEY,
    ),
    secretKey: this.configService.get<EnvironmentalVariables>(
      EnvironmentalVariables.ALPACA_SECRET_KEY,
    ),
    paper: true,
  })

  constructor(private configService: ConfigService) {}

  onModuleInit(): void {
    this.logger.log(`Initializing ${BotService.name}`)
    this.run()
  }

  async run(): Promise<void> {
    const botType: BotType = this.configService.get<BotType>(
      EnvironmentalVariables.BOT_TYPE,
    )

    if (botType === BotType.LONG_SHORT) {
      this.logger.log('Initializing Long Short algorithm')
      await this.longShort.run()
    } else if (botType === BotType.MEAN_REVERSION) {
      this.logger.log('Initializing Mean Reversion algorithm')
      await this.meanReversion.run()
    } else {
      this.logger.error(
        'Please include a valid BOT_TYPE env variable',
        BotService.name,
      )
    }
  }
}
Example #11
Source File: database.test.service.ts    From nestjs-angular-starter with MIT License 5 votes vote down vote up
/**
 * The database test service mocks the original database service, by creating a mongo-in-memory server
 * instead of using the real database.
 */
@Injectable()
export class DatabaseTestService
  implements IDatabaseService, OnModuleInit, OnModuleDestroy {
  // A singletone approach to get the handle to the database on tests and easily close it
  static instance: DatabaseTestService;

  // Create an instance of the in-memory mongo database
  protected mongoServer = new MongoMemoryServer();

  constructor() {
    // Set the instance
    DatabaseTestService.instance = this;
  }

  async onModuleInit(): Promise<void> {
    await this.connect();
  }

  /**
   * Sets up the in-memory mongo database, connects to the database and starts the server.
   */
  async connect(): Promise<void> {
    const uri = await this.mongoServer.getUri();
    await mongoose.connect(uri, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      useCreateIndex: true,
    });
    await TestDBSetup.instance.setup();
  }

  /**
   * Closes the database connection and it's related mongo server emulator.
   */
  async close(): Promise<void> {
    const db = mongoose.connection;
    if (db) await db.close();
    await this.mongoServer.stop();
  }

  async onModuleDestroy(): Promise<void> {
    await this.close();
  }
}
Example #12
Source File: database.service.ts    From nestjs-angular-starter with MIT License 5 votes vote down vote up
/**
 * Responsible of connecting and closing the mongo database connection, it will reconnect a few times
 * if connection cannot be established on the first time.
 */
@Injectable()
export class DatabaseService
  implements IDatabaseService, OnModuleInit, OnModuleDestroy {
  connectionManager: DatabaseConnectionManager =
    DatabaseConnectionManager.instance;
  connection: mongoose.Connection;

  constructor(
    @Inject('DATABASE_MODULE_CONFIG')
    private readonly config: DatabaseModuleConfig,
  ) {}

  /**
   * This forces the database to connect when the database module is imported.
   */
  async onModuleInit(): Promise<void> {
    await this.connectWithRetry(this.config.retryCount || 8);
  }

  protected async connectWithRetry(retryCount: number): Promise<void> {
    for (let i = 0; i < retryCount; i++) {
      try {
        await this.connect();
        return;
      } catch (error) {
        Logger.log(`Retrying to connect database in 5 seconds...`);
        await sleepAsync(5000);
      }
    }
  }

  async connect(): Promise<void> {
    this.connection = await this.connectionManager.connectDatabase(
      this.config.uri,
    );
  }

  async close(): Promise<void> {
    const db = mongoose.connection;
    if (db) await db.close();
  }

  /**
   * Closes the database when the app is being closed.
   */
  async onModuleDestroy(): Promise<void> {
    await this.close();
  }
}
Example #13
Source File: axios-interceptor.ts    From nest-axios-interceptor with MIT License 5 votes vote down vote up
export abstract class AxiosInterceptor<
  TRequestConfig extends AxiosRequestConfig = AxiosRequestConfig,
  TResponse extends AxiosResponse = AxiosResponseCustomConfig<TRequestConfig>,
  TAxiosError extends AxiosError = AxiosErrorCustomConfig<TRequestConfig>
> implements OnModuleInit
{
  protected readonly httpService: HttpService;

  constructor(httpService: HttpService) {
    this.httpService = httpService;
  }

  public onModuleInit(): void {
    this.registerInterceptors();
  }

  private registerInterceptors(): void {
    const { axiosRef: axios } = this.httpService;

    type RequestManager = AxiosInterceptorManager<TRequestConfig>;
    type ResponseManager = AxiosInterceptorManager<TResponse>;

    (axios.interceptors.request as RequestManager).use(
      this.requestFulfilled(),
      this.requestRejected()
    );

    (axios.interceptors.response as ResponseManager).use(
      this.responseFulfilled(),
      this.responseRejected()
    );
  }

  /**
   * Implement this function to do something before request is sent.
   */
  protected requestFulfilled(): AxiosFulfilledInterceptor<TRequestConfig> {
    // Noop by default
    return identityFulfilled;
  }

  /**
   * Implement this function to do something with request error.
   */
  protected requestRejected(): AxiosRejectedInterceptor {
    // Noop by default
    return identityRejected;
  }

  /**
   * Implement this function to do something with response data.
   */
  protected responseFulfilled(): AxiosFulfilledInterceptor<TResponse> {
    // Noop by default
    return identityFulfilled;
  }

  /**
   * Implement this function to do something with response error.
   */
  protected responseRejected(): AxiosRejectedInterceptor {
    // Noop by default
    return identityRejected;
  }

  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
  protected isAxiosError(err: any): err is TAxiosError {
    return !!(err.isAxiosError && err.isAxiosError === true);
  }
}
Example #14
Source File: async-context.ts    From nest-xray with MIT License 5 votes vote down vote up
export class AsyncContext implements OnModuleInit, OnModuleDestroy {
  public static getInstance(): AsyncContext {
    if (!this.instance) {
      this.initialize();
    }
    return this.instance;
  }

  private static instance: AsyncContext;

  private static initialize() {
    const asyncHooksStorage = new AsyncHooksStorage();
    const asyncHook = AsyncHooksHelper.createHooks(asyncHooksStorage);
    const storage = asyncHooksStorage.getInternalStorage();

    this.instance = new AsyncContext(storage, asyncHook);
  }

  private constructor(
    private readonly internalStorage: Map<number, any>,
    private readonly asyncHookRef: asyncHooks.AsyncHook
  ) {}

  public onModuleInit() {
    this.asyncHookRef.enable();
  }

  public onModuleDestroy() {
    this.asyncHookRef.disable();
  }

  public set<TKey = any, TValue = any>(key: TKey, value: TValue) {
    const store = this.getAsyncStorage();
    store.set(key, value);
  }

  public get<TKey = any, TReturnValue = any>(key: TKey): TReturnValue {
    const store = this.getAsyncStorage();
    return store.get(key) as TReturnValue;
  }

  public run(fn: Function) {
    const eid = asyncHooks.executionAsyncId();
    this.internalStorage.set(eid, new Map());
    fn();
  }

  private getAsyncStorage(): Map<unknown, unknown> {
    const eid = asyncHooks.executionAsyncId();
    const state = this.internalStorage.get(eid);
    if (!state) {
      throw new UnknownAsyncContextException(eid);
    }
    return state;
  }
}
Example #15
Source File: kafka.service.ts    From nestjs-kafka with The Unlicense 4 votes vote down vote up
@Injectable()
export class KafkaService implements OnModuleInit, OnModuleDestroy {

  private kafka: Kafka;
  private producer: Producer;
  private consumer: Consumer;
  private admin: Admin;
  private deserializer: Deserializer;
  private serializer: Serializer;
  private autoConnect: boolean;
  private options: KafkaModuleOption['options'];

  protected topicOffsets: Map<string, (SeekEntry & { high: string; low: string })[]> = new Map();
  
  protected logger = new Logger(KafkaService.name);

  constructor(
    options: KafkaModuleOption['options']
  ) {
    const { 
      client,
      consumer: consumerConfig,
      producer: producerConfig,
    } = options;

    this.kafka = new Kafka({
      ...client,
      logCreator: KafkaLogger.bind(null, this.logger)
    });

    const { groupId } = consumerConfig;
    const consumerOptions = Object.assign(
      {
        groupId: this.getGroupIdSuffix(groupId),
      },
      consumerConfig
    );
    
    this.autoConnect = options.autoConnect ?? true;
    this.consumer = this.kafka.consumer(consumerOptions);
    this.producer = this.kafka.producer(producerConfig);
    this.admin = this.kafka.admin();

    this.initializeDeserializer(options);
    this.initializeSerializer(options);
    this.options = options;
  }

  async onModuleInit(): Promise<void> {
    await this.connect();
    await this.getTopicOffsets();
    SUBSCRIBER_MAP.forEach((functionRef, topic) => {
      this.subscribe(topic);
    });
    this.bindAllTopicToConsumer();
  }

  async onModuleDestroy(): Promise<void> {
    await this.disconnect();
  }

  /**
   * Connect the kafka service.
   */
  async connect(): Promise<void> {
    if (!this.autoConnect) {
      return;
    }

    await this.producer.connect()
    await this.consumer.connect();
    await this.admin.connect();
  }

  /**
   * Disconnects the kafka service.
   */
  async disconnect(): Promise<void> {
    await this.producer.disconnect();
    await this.consumer.disconnect();
    await this.admin.disconnect();
  }

  /**
   * Gets the high, low and partitions of a topic.
   */
  private async getTopicOffsets(): Promise<void> {
    const topics = SUBSCRIBER_MAP.keys();

    for await (const topic of topics) {
      try {
        const topicOffsets = await this.admin.fetchTopicOffsets(topic);
        this.topicOffsets.set(topic, topicOffsets);
      } catch (e) {
        this.logger.error('Error fetching topic offset: ', topic);
      }
    }
  }

  /**
   * Subscribes to the topics.
   * 
   * @param topic 
   */
  private async subscribe(topic: string): Promise<void> {
    await this.consumer.subscribe({
      topic,
      fromBeginning: this.options.consumeFromBeginning || false
    });
  }
  
  /**
   * Send/produce a message to a topic.
   * 
   * @param message 
   */
  async send(message: KafkaMessageSend): Promise<RecordMetadata[]> {
    if (!this.producer) {
      this.logger.error('There is no producer, unable to send message.')
      return;
    }

    const serializedPacket = await this.serializer.serialize(message);

    // @todo - rather than have a producerRecord, 
    // most of this can be done when we create the controller.
    return await this.producer.send(serializedPacket);
  }

  /**
   * Gets the groupId suffix for the consumer.
   * 
   * @param groupId 
   */
  public getGroupIdSuffix(groupId: string): string {
    return groupId + '-client';
  }

  /**
   * Calls the method you are subscribed to.
   * 
   * @param topic
   *  The topic to subscribe to.
   * @param instance 
   *  The class instance.
   */
  subscribeToResponseOf<T>(topic: string, instance: T): void {
    SUBSCRIBER_OBJECT_MAP.set(topic, instance);
  }

  /**
   * Returns a new producer transaction in order to produce messages and commit offsets together
   */
  async transaction(): Promise<KafkaTransaction> {
    const producer = this.producer;
    if (!producer) {
      const msg = 'There is no producer, unable to start transactions.';
      this.logger.error(msg);
      throw msg;
    }

    const tx = await producer.transaction();
    const retval: KafkaTransaction = {
      abort(): Promise<void> {
        return tx.abort();
      },
      commit(): Promise<void> {
        return tx.commit();
      },
      isActive(): boolean {
        return tx.isActive();
      },
      async send(message: KafkaMessageSend): Promise<RecordMetadata[]> {
        const serializedPacket = await this.serializer.serialize(message);
        return await tx.send(serializedPacket);
      },
      sendOffsets(offsets: Offsets & { consumerGroupId: string }): Promise<void> {
        return tx.sendOffsets(offsets);
      },
    };
    return retval;
  }

  /**
   * Commit consumer offsets manually.
   * Please note that in most cases you will want to use the given __autoCommitThreshold__
   * or use a transaction to atomically set offsets and outgoing messages.
   *
   * @param topicPartitions
   */
  async commitOffsets(topicPartitions: Array<TopicPartitionOffsetAndMetadata>): Promise<void> {
    return this.consumer.commitOffsets(topicPartitions);
  }

  /**
   * Sets up the serializer to encode outgoing messages.
   * 
   * @param options 
   */
  protected initializeSerializer(options: KafkaModuleOption['options']): void {
    this.serializer = (options && options.serializer) || new KafkaRequestSerializer();
  }

  /**
   * Sets up the deserializer to decode incoming messages.
   * 
   * @param options 
   */
  protected initializeDeserializer(options: KafkaModuleOption['options']): void {
    this.deserializer = (options && options.deserializer) || new KafkaResponseDeserializer();
  }

  /**
   * Runs the consumer and calls the consumers when a message arrives.
   */
  private bindAllTopicToConsumer(): void {
    const runConfig = (this.options.consumerRunConfig) ? this.options.consumerRunConfig : {};
    this.consumer.run({
      ...runConfig,
      eachMessage: async ({ topic, partition, message }) => {
        const objectRef = SUBSCRIBER_OBJECT_MAP.get(topic);
        const callback = SUBSCRIBER_MAP.get(topic);

        try {
          const { timestamp, response, offset, key, headers } = await this.deserializer.deserialize(message, { topic });
          await callback.apply(objectRef, [response, key, offset, timestamp, partition, headers]);
        } catch(e) {
          this.logger.error(`Error for message ${topic}: ${e}`);

          // Log and throw to ensure we don't keep processing the messages when there is an error.
          throw e;
        }
      },
    });

    if (this.options.seek !== undefined) {
      this.seekTopics();
    }
  }

  /**
   * Seeks to a specific offset defined in the config
   * or to the lowest value and across all partitions.
   */
  private seekTopics(): void {
    Object.keys(this.options.seek).forEach((topic) => {
      const topicOffsets = this.topicOffsets.get(topic);
      const seekPoint = this.options.seek[topic];

      topicOffsets.forEach((topicOffset) => {
        let seek = String(seekPoint);

        // Seek by timestamp
        if (typeof seekPoint == 'object') {
          const time = seekPoint as Date;
          seek = time.getTime().toString();
        }

        // Seek to the earliest timestamp.
        if (seekPoint === 'earliest') {
          seek = topicOffset.low;
        }

        this.consumer.seek({
          topic,
          partition: topicOffset.partition,
          offset: seek
        });
      })
    })
  }
}
Example #16
Source File: system.provider.ts    From relate with GNU General Public License v3.0 4 votes vote down vote up
@Injectable()
export class SystemProvider implements OnModuleInit {
    protected readonly dirPaths = {
        ...envPaths(),
        environmentsConfig: path.join(envPaths().config, ENVIRONMENTS_DIR_NAME),
    };

    protected allEnvironments = Dict.from<Map<string, EnvironmentAbstract>>(new Map());

    constructor(@Inject(ConfigService) private readonly configService: ConfigService) {}

    async onModuleInit(): Promise<void> {
        await ensureDirs(this.dirPaths);
        await verifyAcceptedTerms();
        await this.reloadEnvironments();
    }

    async useEnvironment(nameOrId: string): Promise<EnvironmentAbstract> {
        // Get the environment before modifying any config to make sure we don't
        // make any changes in case it doesn't exist.
        const defaultEnvironment = await this.getEnvironment(nameOrId);

        await this.allEnvironments.values
            .mapEach(async (env) => {
                await env.updateConfig('active', env.id === defaultEnvironment.id);
            })
            .unwindPromises();

        return defaultEnvironment;
    }

    async getEnvironment(nameOrId?: string): Promise<EnvironmentAbstract> {
        await this.reloadEnvironments();

        if (nameOrId) {
            const environment: Maybe<EnvironmentAbstract> = this.allEnvironments.values.find(
                (env) => env.id === nameOrId || env.name === nameOrId,
            );

            return environment.flatMap((env) => {
                if (None.isNone(env)) {
                    throw new NotFoundError(`Environment "${nameOrId}" not found`);
                }

                return env;
            });
        }

        const configId = this.configService.get('defaultEnvironmentNameOrId');
        if (configId) {
            const environment: Maybe<EnvironmentAbstract> = this.allEnvironments.values.find(
                (env) => env.id === configId || env.name === configId,
            );

            return environment.flatMap((env) => {
                if (None.isNone(env)) {
                    throw new NotFoundError(`Environment "${configId}" not found`);
                }

                return env;
            });
        }

        const activeEnvironment = this.allEnvironments.values.find((env) => env.isActive);

        return activeEnvironment.flatMap((env) => {
            if (None.isNone(env)) {
                throw new NotFoundError(`No environment in use`, [
                    'Run relate env:use <environment> first to set an active environment',
                ]);
            }

            return env;
        });
    }

    async registerAccessToken(
        environmentNameOrId: string,
        dbmsId: string,
        dbmsUser: string,
        accessToken: string,
    ): Promise<string> {
        const environment = await this.getEnvironment(environmentNameOrId);

        await fse.ensureDir(path.join(environment.dataPath, RELATE_ACCESS_TOKENS_DIR_NAME));
        await registerSystemAccessToken(
            path.join(environment.dataPath, RELATE_ACCESS_TOKENS_DIR_NAME),
            environmentNameOrId,
            dbmsId,
            dbmsUser,
            accessToken,
        );

        return accessToken;
    }

    async getAccessToken(environmentNameOrId: string, dbmsId: string, dbmsUser: string): Promise<string> {
        const environment = await this.getEnvironment(environmentNameOrId);
        const dbms = await environment.dbmss.get(dbmsId);
        const token = await getSystemAccessToken(
            path.join(environment.dataPath, RELATE_ACCESS_TOKENS_DIR_NAME),
            environment.id,
            dbms.id,
            dbmsUser,
        );

        if (!token) {
            throw new NotFoundError(`No Access Token found for user "${dbmsUser}"`);
        }

        return token;
    }

    async createEnvironment(config: IEnvironmentConfigInput): Promise<EnvironmentAbstract> {
        const newId = uuidv4();
        const fileName = `${config.name}${JSON_FILE_EXTENSION}`;
        const filePath = path.join(this.dirPaths.environmentsConfig, fileName);
        const environmentExists = await this.getEnvironment(config.name).catch(() => null);

        if (environmentExists) {
            throw new TargetExistsError(`Environment "${config.name}" exists, will not overwrite`);
        }

        const configModel = new EnvironmentConfigModel({
            relateDataPath: path.join(envPaths().data, `${ENTITY_TYPES.ENVIRONMENT}-${newId}`),
            ...config,
            configPath: filePath,
            id: newId,
        });
        const environment = await createEnvironmentInstance(configModel);

        await fse.writeJSON(filePath, configModel, {
            encoding: 'utf-8',
            spaces: 2,
        });
        this.allEnvironments.setValue(environment.id, environment);

        return environment;
    }

    private async reloadEnvironments(): Promise<void> {
        const configs = await List.from(await fse.readdir(this.dirPaths.environmentsConfig).catch(() => []))
            .filter((name) => name.endsWith('.json'))
            .mapEach((name) => {
                const configPath = path.join(this.dirPaths.environmentsConfig, name);

                return fse
                    .readJSON(configPath)
                    .then(
                        (config) =>
                            new EnvironmentConfigModel({
                                ...config,
                                configPath,
                                relateDataPath: config.relateDataPath || this.dirPaths.data,
                            }),
                    )
                    .catch(() => None.EMPTY);
            })
            .unwindPromises();
        const instances = await configs
            .compact()
            .mapEach((environmentConfig) => createEnvironmentInstance(environmentConfig))
            .unwindPromises();

        this.allEnvironments = Dict.from(instances.mapEach((env): [string, EnvironmentAbstract] => [env.id, env]));
    }

    async listEnvironments(): Promise<List<EnvironmentAbstract>> {
        await this.reloadEnvironments();

        return this.allEnvironments.values;
    }

    async handleFileUpload(fileName: string, readStream: Readable): Promise<string> {
        const tmpDir = path.join(envPaths().tmp, uuidv4());
        const tmpFileName = path.join(tmpDir, `${uuidv4()}.rdownload`);

        await fse.ensureDir(tmpDir);

        try {
            const uploadPromise = new Promise<void>((resolve, reject) =>
                readStream
                    .pipe(fse.createWriteStream(tmpFileName))
                    .on('finish', () => resolve())
                    .on('error', (err) => reject(err)),
            );

            await uploadPromise;

            const uploadedFileName = path.join(tmpDir, fileName);

            await fse.move(tmpFileName, uploadedFileName);

            return uploadedFileName;
        } catch (_e) {
            throw new FileUploadError(`Failed to upload file ${fileName}`);
        }
    }
}
Example #17
Source File: service-bus.module.ts    From pebula-node with MIT License 4 votes vote down vote up
@Module({
  providers: [
    SbDiscoveryFactoryService,
  ],
})
export class ServiceBusModule implements OnModuleInit, OnModuleDestroy {

  /**
   * Register a service bus server/s that will be used as the underlying resources to generate `Queue` & `Subscription` listeners.
   *
   * You can provide multiple server configurations, however make sure that each of them has a unique name.
   * Note that not setting a name is a unique name by itself.
   *
   */
  static register(options: SbModuleRegisterOptions): DynamicModule {
    const providers: Provider[] = [];

    if (Array.isArray(options.servers)) {
      providers.push({
        provide: SB_SERVER_OPTIONS,
        useValue: options.servers,
      });
    } else {
      providers.push({
        provide: SB_SERVER_OPTIONS,
        ...options.servers,
      });
    }

    if (Array.isArray(options.clients)) {
      providers.push({
        provide: SB_CLIENT_OPTIONS,
        useValue: options.clients,
      });
    } else {
      providers.push({
        provide: SB_CLIENT_OPTIONS,
        ...options.clients,
      });
    }

    if (options.metaFactoryProvider) {
      providers.push(normalizeProvider(options.metaFactoryProvider));
    }

    if (Array.isArray(options.providers)) {
      providers.push(...options.providers);
    }

    return { module: ServiceBusModule, providers };
  }

  private discovery: SbDiscoveryService;

  constructor(discoveryFactory: SbDiscoveryFactoryService,
              @Optional() errorHandler?: SbErrorHandler,
              @Optional() @Inject(SB_META_HELPER_FACTORY_TOKEN) metadataHelper?: any,
              @Optional() @Inject(SB_CLIENT_OPTIONS) clientOptions?: SbClientOptions[],
              @Optional() @Inject(SB_SERVER_OPTIONS) serverOptions?: SbServerOptions[]) {
    if (!Array.isArray(serverOptions) || serverOptions.length === 0) {
      throw new Error('You must define at least 1 server, did you use `ServiceBusModule.register()` ?');
    }

    if (errorHandler) {
      sbResourceManager.errorHandler = errorHandler;
    }

    this.discovery = discoveryFactory.create(
      serverOptions,
      !Array.isArray(clientOptions) || clientOptions.length === 0 ? [{}] : clientOptions,
      metadataHelper,
    );

    this.discovery.init();
  }

  async onModuleInit(): Promise<void> {
    await this.discovery.discover();
  }

  async onModuleDestroy(): Promise<void> {
    await this.discovery.destroy();
  }
}
Example #18
Source File: sqs.service.ts    From nestjs-sqs with MIT License 4 votes vote down vote up
@Injectable()
export class SqsService implements OnModuleInit, OnModuleDestroy {
  public readonly consumers = new Map<QueueName, Consumer>();
  public readonly producers = new Map<QueueName, Producer>();

  private readonly logger = new Logger('SqsService', {
    timestamp: false,
  });

  public constructor(
    @Inject(SQS_OPTIONS) public readonly options: SqsOptions,
    private readonly discover: DiscoveryService,
  ) {}

  public async onModuleInit(): Promise<void> {
    const messageHandlers = await this.discover.providerMethodsWithMetaAtKey<SqsMessageHandlerMeta>(
      SQS_CONSUMER_METHOD,
    );
    const eventHandlers = await this.discover.providerMethodsWithMetaAtKey<SqsConsumerEventHandlerMeta>(
      SQS_CONSUMER_EVENT_HANDLER,
    );

    this.options.consumers?.forEach((options) => {
      const { name, ...consumerOptions } = options;
      if (this.consumers.has(name)) {
        throw new Error(`Consumer already exists: ${name}`);
      }

      const metadata = messageHandlers.find(({ meta }) => meta.name === name);
      if (!metadata) {
        this.logger.warn(`No metadata found for: ${name}`);
      }

      const isBatchHandler = metadata.meta.batch === true;
      const consumer = Consumer.create({
        ...consumerOptions,
        ...(isBatchHandler
          ? {
              handleMessageBatch: metadata.discoveredMethod.handler.bind(
                metadata.discoveredMethod.parentClass.instance,
              ),
            }
          : { handleMessage: metadata.discoveredMethod.handler.bind(metadata.discoveredMethod.parentClass.instance) }),
      });

      const eventsMetadata = eventHandlers.filter(({ meta }) => meta.name === name);
      for (const eventMetadata of eventsMetadata) {
        if (eventMetadata) {
          consumer.addListener(
            eventMetadata.meta.eventName,
            eventMetadata.discoveredMethod.handler.bind(metadata.discoveredMethod.parentClass.instance),
          );
        }
      }
      this.consumers.set(name, consumer);
    });

    this.options.producers?.forEach((options) => {
      const { name, ...producerOptions } = options;
      if (this.producers.has(name)) {
        throw new Error(`Producer already exists: ${name}`);
      }

      const producer = Producer.create(producerOptions);
      this.producers.set(name, producer);
    });

    for (const consumer of this.consumers.values()) {
      consumer.start();
    }
  }

  public onModuleDestroy() {
    for (const consumer of this.consumers.values()) {
      consumer.stop();
    }
  }

  private getQueueInfo(name: QueueName) {
    if (!this.consumers.has(name) && !this.producers.has(name)) {
      throw new Error(`Consumer/Producer does not exist: ${name}`);
    }

    const { sqs, queueUrl } = (this.consumers.get(name) ?? this.producers.get(name)) as {
      sqs: AWS.SQS;
      queueUrl: string;
    };
    if (!sqs) {
      throw new Error('SQS instance does not exist');
    }

    return {
      sqs,
      queueUrl,
    };
  }

  public async purgeQueue(name: QueueName) {
    const { sqs, queueUrl } = this.getQueueInfo(name);
    return sqs
      .purgeQueue({
        QueueUrl: queueUrl,
      })
      .promise();
  }

  public async getQueueAttributes(name: QueueName) {
    const { sqs, queueUrl } = this.getQueueInfo(name);
    const response = await sqs
      .getQueueAttributes({
        QueueUrl: queueUrl,
        AttributeNames: ['All'],
      })
      .promise();
    return response.Attributes as { [key in QueueAttributeName]: string };
  }

  public getProducerQueueSize(name: QueueName) {
    if (!this.producers.has(name)) {
      throw new Error(`Producer does not exist: ${name}`);
    }

    return this.producers.get(name).queueSize();
  }

  public send<T = any>(name: QueueName, payload: Message<T> | Message<T>[]) {
    if (!this.producers.has(name)) {
      throw new Error(`Producer does not exist: ${name}`);
    }

    const originalMessages = Array.isArray(payload) ? payload : [payload];
    const messages = originalMessages.map((message) => {
      let body = message.body;
      if (typeof body !== 'string') {
        body = JSON.stringify(body) as any;
      }

      return {
        ...message,
        body,
      };
    });

    const producer = this.producers.get(name);
    return producer.send(messages as any[]);
  }
}
Example #19
Source File: oauth2-core.module.ts    From nestjs-oauth2-server-module with MIT License 4 votes vote down vote up
@Global()
@Module({})
export class Oauth2CoreModule implements OnModuleInit {
    constructor(
        @Inject(OAUTH2_SERVER_OPTIONS)
        private readonly options: OAuth2Options,
        private readonly explorerService: StrategyExplorer,
        private readonly strategyRegistry: Oauth2GrantStrategyRegistry,
    ) {
    }

    /**
     * Create the static for Root Options
     *
     * @param options
     */
    public static forRoot(options: OAuth2Options): DynamicModule {

        const oAuth2OptionsProvider = {
            provide: OAUTH2_SERVER_OPTIONS,
            useValue: options,
        };

        const userLoaderProvider = {
            provide: 'UserLoaderInterface',
            useFactory: async (options) => {
                return options.userLoader;
            },
            inject: [OAUTH2_SERVER_OPTIONS],
        };

        const userValidatorProvider = {
            provide: 'UserValidatorInterface',
            useFactory: async (options) => {
                return options.userValidator;
            },
            inject: [OAUTH2_SERVER_OPTIONS],
        };

        return {
            module: Oauth2CoreModule,
            imports: [
                CqrsModule,
                TypeOrmModule.forFeature([
                    ClientEntity,
                    AccessTokenEntity,
                ]),
            ],
            controllers: [
                Oauth2Controller
            ],
            providers: [
                oAuth2OptionsProvider,
                userValidatorProvider,
                userLoaderProvider,
                ...Providers,
                ...Services,
                ...Resolvers,
                ...Oauth2Strategies,
                ...CommandHandlers,
                ...EventHandlers,
                ...QueryHandlers,
                ...Sagas,
                AccessTokenStrategy,
            ],
            exports: [
                ...Providers,
                ...ServiceNames,
                userValidatorProvider,
                userLoaderProvider
            ]
        };
    }

    public static forRootAsync(options: Oauth2AsyncOptionsInterface): DynamicModule {
        const providers: Provider[] = this.createAsyncProviders(options);

        const userLoaderProvider = {
            provide: 'UserLoaderInterface',
            useFactory: async (options) => {
                return options.userLoader;
            },
            inject: [OAUTH2_SERVER_OPTIONS],
        };

        const userValidatorProvider = {
            provide: 'UserValidatorInterface',
            useFactory: async (options) => {
                return options.userValidator;
            },
            inject: [OAUTH2_SERVER_OPTIONS],
        };

        return {
            module: Oauth2CoreModule,
            imports: [
                ...(options.imports || []),
                CqrsModule,
                TypeOrmModule.forFeature([
                    ClientEntity,
                    AccessTokenEntity,
                ]),
            ],
            providers: [
                ...providers,
                userValidatorProvider,
                userLoaderProvider,
                ...Providers,
                ...Services,
                ...Resolvers,
                ...Oauth2Strategies,
                ...CommandHandlers,
                ...EventHandlers,
                ...QueryHandlers,
                ...Sagas,
                AccessTokenStrategy,
            ],
            controllers: [
                Oauth2Controller
            ],
            exports: [
                ...Providers,
                ...ServiceNames,
                userValidatorProvider,
                userLoaderProvider
            ]
        };
    }

    private static createAsyncProviders(options: Oauth2AsyncOptionsInterface): Provider[] {
        const providers: Provider[] = [
            this.createAsyncOptionsProvider(options),
        ];

        return providers;
    }

    private static createAsyncOptionsProvider(options: Oauth2AsyncOptionsInterface): Provider {
        if (options.useFactory) {
            return {
                provide: OAUTH2_SERVER_OPTIONS,
                useFactory: options.useFactory,
                inject: options.inject || [],
            };
        }

        return {
            provide: OAUTH2_SERVER_OPTIONS,
            useFactory: async (optionsFactory: Oauth2OptionsFactoryInterface) => {
                return optionsFactory.createOauth2Options();
            },
            inject: [options.useExisting || options.useClass],
        };
    }

    onModuleInit() {
        const {strategies} = this.explorerService.explore();
        this.strategyRegistry.register(strategies);
    }
}
Example #20
Source File: queue.module.ts    From nest-amqp with MIT License 4 votes vote down vote up
@Module({})
export class QueueModule implements OnModuleInit, OnModuleDestroy {
  private static readonly moduleDefinition: DynamicModule = {
    global: false,
    module: QueueModule,
    providers: [AMQPService, QueueService, MetadataScanner, ListenerExplorer, ObjectValidatorService],
    exports: [QueueService],
  };

  public static forRoot(options: QueueModuleOptions): DynamicModule;
  public static forRoot(connectionUri: string): DynamicModule;
  public static forRoot(connectionUri: string, options: Omit<QueueModuleOptions, 'connectionUri'>): DynamicModule;
  public static forRoot(connections: NamedAMQPConnectionOptions[], options?: MultiConnectionQueueModuleOptions): DynamicModule;
  public static forRoot(
    connectionUri: string | QueueModuleOptions | NamedAMQPConnectionOptions[],
    options: Omit<QueueModuleOptions, 'connectionUri'> | MultiConnectionQueueModuleOptions = {},
  ): DynamicModule {
    const queueModuleOptionsProviders = [];
    const connectionProviders = [];
    const connectionOptionsProviders = [];

    if (toString.call(connectionUri) === '[object Array]') {
      queueModuleOptionsProviders.push(QueueModule.getQueueModuleOptionsProvider(options));
      for (const connectionOptions of connectionUri as NamedAMQPConnectionOptions[]) {
        connectionOptionsProviders.push(QueueModule.getAMQPConnectionOptionsProvider(connectionOptions, connectionOptions.name));
        connectionProviders.push(QueueModule.getConnectionProvider(connectionOptions.name));
      }
    } else {
      const moduleOptions = typeof connectionUri === 'string' ? { ...options, connectionUri } : (connectionUri as QueueModuleOptions);
      queueModuleOptionsProviders.push(QueueModule.getQueueModuleOptionsProvider(moduleOptions));
      connectionOptionsProviders.push(QueueModule.getAMQPConnectionOptionsProvider(moduleOptions));
      connectionProviders.push(QueueModule.getConnectionProvider(AMQP_DEFAULT_CONNECTION_TOKEN));
    }

    Object.assign(QueueModule.moduleDefinition, {
      global: !!options.isGlobal,
      providers: [
        ...queueModuleOptionsProviders,
        ...QueueModule.moduleDefinition.providers,
        ...connectionOptionsProviders,
        ...connectionProviders,
      ],
    });

    return QueueModule.moduleDefinition;
  }

  public static forRootAsync(options: QueueModuleAsyncOptions): DynamicModule {
    // TODO - allow for multiple connections
    const connectionProviders = [QueueModule.getConnectionProvider(AMQP_DEFAULT_CONNECTION_TOKEN)];

    const asyncProviders = this.createAsyncProviders(options);

    Object.assign(QueueModule.moduleDefinition, {
      global: !!options.isGlobal,
      imports: options.imports,
      providers: [...asyncProviders, ...QueueModule.moduleDefinition.providers, ...connectionProviders],
    });

    return QueueModule.moduleDefinition;
  }

  public static forFeature(): DynamicModule {
    return QueueModule.moduleDefinition;
  }

  private static createAsyncProviders(options: QueueModuleAsyncOptions): Provider[] {
    if (!options.useClass && !options.useExisting && !options.useFactory) {
      throw new Error('Must provide factory, class or existing provider');
    }

    if (options.useExisting || options.useFactory) {
      return [this.createAsyncQueueModuleOptionsProvider(options), this.createAsyncAMQConnectionsOptionsProvider(options)];
    }

    const useClass = options.useClass as Type<QueueModuleOptionsFactory>;

    return [
      this.createAsyncQueueModuleOptionsProvider(options),
      this.createAsyncAMQConnectionsOptionsProvider(options),
      {
        provide: useClass,
        useClass,
      },
    ];
  }

  private static createAsyncQueueModuleOptionsProvider(options: QueueModuleAsyncOptions): Provider {
    if (options.useFactory) {
      return {
        provide: QUEUE_MODULE_OPTIONS,
        useFactory: options.useFactory,
        inject: options.inject || [],
      };
    }

    const inject = [options.useClass ?? options.useExisting];

    return {
      provide: QUEUE_MODULE_OPTIONS,
      useFactory: async (factory: QueueModuleOptionsFactory): Promise<QueueModuleOptions> => factory.createQueueModuleOptions(),
      inject,
    };
  }

  private static createAsyncAMQConnectionsOptionsProvider(options: QueueModuleAsyncOptions): Provider {
    if (options.useFactory) {
      return {
        provide: getAMQConnectionOptionsToken(AMQP_DEFAULT_CONNECTION_TOKEN),
        inject: options.inject || [],
        useFactory: async (...args: any[]) => {
          const moduleOptions = await options.useFactory(...args);
          const useValue = QueueModule.getConnectionOptions(moduleOptions);

          AMQConnectionOptionsStorage.add(AMQP_DEFAULT_CONNECTION_TOKEN, useValue);

          return moduleOptions;
        },
      };
    }

    const inject = [options.useClass ?? options.useExisting];

    return {
      provide: getAMQConnectionOptionsToken(AMQP_DEFAULT_CONNECTION_TOKEN),
      useFactory: async (optionsFactory: QueueModuleOptionsFactory) => {
        const moduleOptions = await optionsFactory.createQueueModuleOptions();
        const useValue = QueueModule.getConnectionOptions(moduleOptions);

        AMQConnectionOptionsStorage.add(AMQP_DEFAULT_CONNECTION_TOKEN, useValue);

        return moduleOptions;
      },
      inject,
    };
  }

  /**
   * Creates a connection provider with the given name
   *
   * @param {string} connection Name of the connection
   *
   * @returns {Provider} Named Connection provider
   *
   * @private
   * @static
   */
  private static getConnectionProvider(connection: string = AMQP_DEFAULT_CONNECTION_TOKEN): Provider {
    return {
      provide: getAMQConnectionToken(connection),
      useFactory: async (options: AMQPConnectionOptions): Promise<Connection> => AMQPService.createConnection(options, connection),
      inject: [getAMQConnectionOptionsToken(connection)],
    };
  }

  private static getQueueModuleOptionsProvider(options: Partial<QueueModuleOptions>): Provider {
    return {
      provide: QUEUE_MODULE_OPTIONS,
      useValue: options,
    };
  }

  private static getAMQPConnectionOptionsProvider(
    options: AMQPConnectionOptions,
    connection: string = AMQP_DEFAULT_CONNECTION_TOKEN,
  ): Provider {
    const provide = getAMQConnectionOptionsToken(connection);
    const useValue = QueueModule.getConnectionOptions(options);

    AMQConnectionOptionsStorage.add(connection, useValue);

    return { provide, useValue };
  }

  private static getConnectionOptions(options: AMQPConnectionOptions): AMQPConnectionOptions {
    const { connectionOptions, connectionUri, throwExceptionOnConnectionError } = options;

    return {
      connectionUri,
      ...(isDefined(connectionOptions) ? { connectionOptions } : {}),
      ...(isDefined(throwExceptionOnConnectionError) ? { throwExceptionOnConnectionError } : {}),
    };
  }

  constructor(
    @Inject(QUEUE_MODULE_OPTIONS) private readonly moduleOptions: QueueModuleOptions,
    private readonly queueService: QueueService,
    private readonly listenerExplorer: ListenerExplorer,
    private readonly moduleRef: ModuleRef,
  ) {}

  // istanbul ignore next
  public async onModuleInit(): Promise<void> {
    logger.log('initializing queue module');

    if (this.moduleOptions.logger) {
      Logger.overrideLogger(this.moduleOptions.logger);
    }

    // find everything marked with @Listen
    const listeners = this.listenerExplorer.explore();
    await this.attachListeners(listeners);

    AMQPService.eventEmitter.on(AMQP_CONNECTION_RECONNECT, () => {
      logger.log('reattaching receivers to connection');
      this.queueService.clearSenderAndReceiverLinks();
      this.attachListeners(listeners)
        .then(() => logger.log('receivers reattached'))
        .catch(error => logger.error('error while reattaching listeners', error));
    });

    logger.log('queue module initialized');
  }

  public async onModuleDestroy(): Promise<void> {
    logger.log('destroying queue module');

    await this.queueService.shutdown();

    logger.log('queue module destroyed');
  }

  // istanbul ignore next
  private async attachListeners(listeners: Array<ListenerMetadata<unknown>>): Promise<void> {
    // set up listeners
    for (const listener of listeners) {
      logger.debug(`attaching listener for @Listen: ${JSON.stringify(listener)}`);

      // fetch instance from DI framework
      let target: any;
      try {
        target = this.moduleRef.get(listener.target as any, { strict: false });
      } catch (err) {
        if (err instanceof UnknownElementException) {
          target = this.moduleRef.get(listener.targetName, { strict: false });
        } else {
          throw err;
        }
      }

      await this.queueService.listen(listener.source, listener.callback.bind(target), listener.options, listener.connection);
    }
  }
}
Example #21
Source File: app.service.ts    From bank-server with MIT License 4 votes vote down vote up
@Injectable()
export class AppService implements OnModuleInit {
  private readonly _logger = new Logger(AppService.name);
  private readonly _configService = new ConfigService();
  private readonly _moduleOptions = { strict: false };

  private _currencyCron: CurrencyCron;
  private _currencyService: CurrencyService;
  private _languageService: LanguageService;
  private _userService: UserService;
  private _userAuthService: UserAuthService;
  private _messageKeyService: MessageKeyService;

  constructor(private readonly _moduleRef: ModuleRef) {}

  public async onModuleInit(): Promise<void> {
    this._currencyCron = this._moduleRef.get(CurrencyCron, this._moduleOptions);
    this._languageService = this._moduleRef.get(
      LanguageService,
      this._moduleOptions,
    );
    this._userService = this._moduleRef.get(UserService, this._moduleOptions);
    this._userAuthService = this._moduleRef.get(
      UserAuthService,
      this._moduleOptions,
    );
    this._currencyService = this._moduleRef.get(
      CurrencyService,
      this._moduleOptions,
    );
    this._messageKeyService = this._moduleRef.get(
      MessageKeyService,
      this._moduleOptions,
    );

    await this._initExchangeRates();
    await this._initLanguage();
    await this._initMessageKeys();
    await Promise.all([this._initRootUser(), this._initAuthorUser()]);
  }

  private async _initExchangeRates(): Promise<void> {
    await this._currencyCron.setCurrencyForeignExchangeRates();
    this._logger.log(`Exchange rates have been initiated`);
  }

  private async _initLanguage(): Promise<void> {
    await this._languageService.setLanguages();
    this._logger.log(`Languages have been initiated`);
  }

  private async _initMessageKeys(): Promise<void> {
    await this._messageKeyService.setMessageKeys();
    this._logger.log(`Message keys have been initiated`);
  }

  private async _initRootUser(): Promise<void> {
    const rootEmail = this._configService.get('BANK_ROOT_EMAIL');
    const rootPassword = this._configService.get('BANK_ROOT_PASSWORD');

    const isExistRootUser = await this._userService.getUser({
      email: rootEmail,
    });

    if (isExistRootUser) {
      return;
    }

    const { uuid } = await this._currencyService.findCurrency({
      name: 'USD',
    });

    const { userAuth } = await this._userService.createUser({
      firstName: 'Bank',
      lastName: 'Application',
      email: rootEmail,
      password: rootPassword,
      currency: uuid,
    });

    await this._userAuthService.updateRole(userAuth, RoleType.ROOT);

    this._logger.log(`Root user have been initiated`);
  }

  private async _initAuthorUser(): Promise<void> {
    const authorEmail = this._configService.get('BANK_AUTHOR_EMAIL');
    const authorPassword = this._configService.get('BANK_AUTHOR_PASSWORD');
    const authorFirstName = this._configService.get('BANK_AUTHOR_FIRSTNAME');
    const authorLastName = this._configService.get('BANK_AUTHOR_LASTNAME');

    const isExistAuthorUser = await this._userService.getUser({
      email: authorEmail,
    });

    if (isExistAuthorUser) {
      return;
    }

    const { uuid } = await this._currencyService.findCurrency({
      name: 'USD',
    });

    const { userAuth } = await this._userService.createUser({
      firstName: authorFirstName,
      lastName: authorLastName,
      email: authorEmail,
      password: authorPassword,
      currency: uuid,
    });

    await this._userAuthService.updateRole(userAuth, RoleType.ADMIN);
    this._logger.log(`Author user have been initiated`);
  }
}
Example #22
Source File: web.module.ts    From relate with GNU General Public License v3.0 4 votes vote down vote up
@Module({
    imports: [
        SystemModule,
        DBMSModule,
        DBMSPluginsModule,
        DBModule,
        ExtensionModule,
        ProjectModule,
        FilesModule,
        GraphQLModule.forRootAsync({
            useFactory: (configService: ConfigService<IWebModuleConfig>, systemProvider: SystemProvider) => ({
                playground: {
                    settings: {
                        'request.credentials': 'same-origin',
                    },
                },
                autoSchemaFile: configService.get('autoSchemaFile'),
                subscriptions: {
                    // @todo - This is temporarily enabled in development
                    // because graphql playground and apollo explorer don't yet
                    // support `graphql-ws`. It shouldn't be enabled in
                    // production because `subscriptions-transport-ws` has a bug
                    // that will allow certain connections to skip the
                    // onConnection handler, bypassing the token check.
                    'subscriptions-transport-ws':
                        process.env.NODE_ENV === 'development' ? subscriptionsTransportWs(systemProvider) : false,
                    'graphql-ws': graphqlWs(systemProvider),
                },
            }),
            imports: [SystemModule],
            inject: [ConfigService, SystemProvider],
        }),
        HealthModule,
        AuthModule,
    ],
})
export class WebModule implements OnModuleInit {
    static register(config: IWebModuleConfig): DynamicModule {
        const {defaultEnvironmentNameOrId} = config;
        const webExtensions = loadExtensionsFor(EXTENSION_TYPES.WEB, defaultEnvironmentNameOrId);

        return {
            imports: [SystemModule.register(config), ...webExtensions],
            module: WebModule,
            exports: [SystemModule, ...webExtensions],
        };
    }

    constructor(
        @Inject(GraphQLSchemaHost) private readonly schemaHost: GraphQLSchemaHost,
        @Inject(HttpAdapterHost) private readonly httpAdapterHost: HttpAdapterHost,
    ) {}

    onModuleInit(): void {
        if (!this.httpAdapterHost) {
            return;
        }

        const {httpAdapter} = this.httpAdapterHost;
        const app: Application = httpAdapter.getInstance();
        const {schema} = this.schemaHost;
        const openApi = OpenAPI({
            schema,
            info: {
                title: 'Relate REST API',
                version: '1.0.0',
            },
        });

        // add multer to file upload endpoints
        const uploads = multer({dest: envPaths().tmp});

        app.use('/api/add-project-file', uploads.single('fileUpload'), (req, _, next) => {
            req.body = {
                ...req.body,
                fileUpload: {
                    // convert multer file object to the same shape as graphql-upload
                    ...req.file,
                    filename: req.file.originalname,
                },
            };
            next();
        });

        // convert GraphQL API to REST using SOFA
        app.use(
            '/api',
            useSofa({
                basePath: '/api',
                schema,
                onRoute(info) {
                    openApi.addRoute(info, {
                        basePath: '/api',
                    });
                },
            }),
        );

        // add Swagger page for REST API
        const openApiDefinitions = openApi.get();
        openApiDefinitions.paths['/api/add-project-file'] = fixAddProjectFilesOpenAPIDef(
            openApiDefinitions.paths['/api/add-project-file'],
        );

        app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openApiDefinitions));
    }
}
Example #23
Source File: tracing.service.ts    From nest-xray with MIT License 4 votes vote down vote up
@Injectable()
export class TracingService implements OnModuleInit {
  private readonly config: Required<TracingConfig>;
  constructor(
    @Inject(XRAY_CLIENT) private readonly xrayClient: XRayClient,
    private readonly asyncContext: AsyncContext,
    options: TracingConfig
  ) {
    this.config = {
      serviceName: options.serviceName || "example-service",
      rate: options.rate !== undefined ? options.rate : 1,
      daemonAddress: options.daemonAddress || "172.17.0.1:2000",
      plugins: options.plugins || [plugins.EC2Plugin, plugins.ECSPlugin],
    }; // Set defaults
  }

  public onModuleInit() {
    // AWSXRay uses continuation-local-storage for the automatic mode, this
    // does not work in async/await Scenarios. We will implement our own
    // "automatic mode"

    this.xrayClient.enableManualMode();

    this.xrayClient.setDaemonAddress(this.config.daemonAddress);
    this.xrayClient.middleware.setSamplingRules(this.getSamplingRules());

    this.xrayClient.config(this.config.plugins);

    // Disable errors on missing context
    this.xrayClient.setContextMissingStrategy(() => {});
  }

  public tracingMiddleware(): RequestHandler {
    return this.xrayClient.express.openSegment(this.config.serviceName);
  }

  public setRootSegment(segment: Segment) {
    try {
      this.asyncContext.set(TRACING_ASYNC_CONTEXT_SEGMENT, segment);
    } catch (err) {
      if (err instanceof UnknownAsyncContextException) {
        throw new TracingNotInitializedException();
      }

      throw err;
    }
  }

  public getRootSegment(): Segment {
    try {
      return this.asyncContext.get<string, Segment>(
        TRACING_ASYNC_CONTEXT_SEGMENT
      );
    } catch (err) {
      if (err instanceof UnknownAsyncContextException) {
        throw new TracingNotInitializedException();
      }

      throw err;
    }
  }

  public setSubSegment(segment: Subsegment) {
    try {
      this.asyncContext.set(TRACING_ASYNC_CONTEXT_SUBSEGMENT, segment);
    } catch (err) {
      if (err instanceof UnknownAsyncContextException) {
        throw new TracingNotInitializedException();
      }

      throw err;
    }
  }

  public getSubSegment(): Subsegment {
    try {
      return this.asyncContext.get<string, Subsegment>(
        TRACING_ASYNC_CONTEXT_SUBSEGMENT
      );
    } catch (err) {
      if (err instanceof UnknownAsyncContextException) {
        throw new TracingNotInitializedException();
      }

      throw err;
    }
  }

  public getTracingHeader(parentSegment: Subsegment): string {
    const rootSegment = this.getRootSegment();
    return `Root=${rootSegment.trace_id};Parent=${parentSegment.id};Sampled=1`;
  }

  public createSubSegment(name: string): Subsegment {
    const rootSegment = this.getRootSegment();

    const subSegment = rootSegment.addNewSubsegment(name);

    return subSegment;
  }

  private getSamplingRules() {
    return {
      rules: [
        {
          description: "LoadBalancer HealthCheck",
          http_method: "GET",
          host: "*",
          url_path: "/status",
          fixed_target: 0,
          rate: 0,
        },
      ],
      default: { fixed_target: 0, rate: this.config.rate },
      version: 2,
    };
  }
}
Example #24
Source File: mqtt.explorer.ts    From nest-mqtt with MIT License 4 votes vote down vote up
@Injectable()
export class MqttExplorer implements OnModuleInit {
  private readonly reflector = new Reflector();
  subscribers: MqttSubscriber[];

  constructor(
    private readonly discoveryService: DiscoveryService,
    private readonly metadataScanner: MetadataScanner,
    @Inject(MQTT_LOGGER_PROVIDER) private readonly logger: Logger,
    @Inject(MQTT_CLIENT_INSTANCE) private readonly client: Client,
    @Inject(MQTT_OPTION_PROVIDER) private readonly options: MqttModuleOptions,
  ) {
    this.subscribers = [];
  }

  onModuleInit() {
    this.logger.log('MqttModule dependencies initialized');
    this.explore();
  }

  preprocess(options: MqttSubscribeOptions): string | string[] {
    const processTopic = (topic) => {
      const queue = typeof options.queue === 'boolean' ? options.queue : this.options.queue;
      const share = typeof options.share === 'string' ? options.share : this.options.share;
      topic = topic.replace('$queue/', '')
        .replace(/^\$share\/([A-Za-z0-9]+)\//, '');
      if (queue) {
        return `$queue/${topic}`;
      }

      if (share) {
        return `$share/${share}/${topic}`;
      }

      return topic;
    };
    if (Array.isArray(options.topic)) {
      return options.topic.map(processTopic);
    } else {
      // this.logger.log(options.topic);
      return processTopic(options.topic);
    }
  }

  subscribe(options: MqttSubscribeOptions, parameters: MqttSubscriberParameter[], handle, provider) {
    this.client.subscribe(this.preprocess(options), err => {
      if (!err) {
        // put it into this.subscribers;
        (Array.isArray(options.topic) ? options.topic : [options.topic])
          .forEach(topic => {
            this.subscribers.push({
              topic,
              route: topic.replace('$queue/', '')
                .replace(/^\$share\/([A-Za-z0-9]+)\//, ''),
              regexp: MqttExplorer.topicToRegexp(topic),
              provider,
              handle,
              options,
              parameters,
            });
          });
      } else {
        this.logger.error(
          `subscribe topic [${options.topic} failed]`,
        );
      }
    });
  }

  explore() {
    const providers: InstanceWrapper[] = this.discoveryService.getProviders();
    providers.forEach((wrapper: InstanceWrapper) => {
      const { instance } = wrapper;
      if (!instance) {
        return;
      }
      this.metadataScanner.scanFromPrototype(
        instance,
        Object.getPrototypeOf(instance),
        key => {
          const subscribeOptions: MqttSubscribeOptions = this.reflector.get(
            MQTT_SUBSCRIBE_OPTIONS,
            instance[key],
          );
          const parameters = this.reflector.get(
            MQTT_SUBSCRIBER_PARAMS,
            instance[key],
          );
          if (subscribeOptions) {
            this.subscribe(subscribeOptions, parameters, instance[key], instance);
          }
        },
      );
    });
    this.client.on(
      'message',
      (topic: string, payload: Buffer, packet: Packet) => {
        const subscriber = this.getSubscriber(topic);
        if (subscriber) {
          const parameters = subscriber.parameters || [];
          const scatterParameters: MqttSubscriberParameter[] = [];
          for (const parameter of parameters) {
            scatterParameters[parameter.index] = parameter;
          }
          try {
            const transform = getTransform(subscriber.options.transform);

            // add a option to do something before handle message.
            if (this.options.beforeHandle) {
              this.options.beforeHandle(topic, payload, packet);
            }

            subscriber.handle.bind(subscriber.provider)(
              ...scatterParameters.map(parameter => {
                switch (parameter?.type) {
                  case 'payload':
                    return transform(payload);
                  case 'topic':
                    return topic;
                  case 'packet':
                    return packet;
                  case 'params':
                    return MqttExplorer.matchGroups(topic, subscriber.regexp);
                  default:
                    return null;
                }
              }),
            );
          } catch (err) {
            this.logger.error(err);
          }
        }
      },
    );
  }

  private getSubscriber(topic: string): MqttSubscriber | null {
    for (const subscriber of this.subscribers) {
      subscriber.regexp.lastIndex = 0;
      if (subscriber.regexp.test(topic)) {
        return subscriber;
      }
    }
    return null;
  }

  private static topicToRegexp(topic: string) {
    // compatible with emqtt
    return new RegExp(
      '^' +
      topic
        .replace('$queue/', '')
        .replace(/^\$share\/([A-Za-z0-9]+)\//, '')
        .replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g, '\\$1')
        .replace(/\+/g, '([^/]+)')
        .replace(/\/#$/, '(/.*)?') +
      '$',
      'y',
    );
  }

  private static matchGroups(str: string, regex: RegExp) {
    regex.lastIndex = 0;
    let m = regex.exec(str);
    const matches: string[] = [];

    while (m !== null) {
      // This is necessary to avoid infinite loops with zero-width matches
      if (m.index === regex.lastIndex) {
        regex.lastIndex++;
      }

      // The result can be accessed through the `m`-variable.
      m.forEach((match, groupIndex) => {
        if (groupIndex !== 0) {
          matches.push(match);
        }
      });
      m = regex.exec(str);
    }
    return matches;
  }
}
Example #25
Source File: bull.explorer.ts    From nestjs-bullmq with MIT License 4 votes vote down vote up
@Injectable()
export class BullExplorer implements OnModuleInit {
  private readonly logger = new Logger('BullModule');
  private readonly injector = new Injector();

  constructor(
    private readonly moduleRef: ModuleRef,
    private readonly discoveryService: DiscoveryService,
    private readonly metadataAccessor: BullMetadataAccessor,
    private readonly metadataScanner: MetadataScanner,
  ) {}

  onModuleInit() {
    this.explore();
  }

  explore() {
    const workers: Record<string, Worker> = {};
    const queueEvents: Record<string, QueueEvents> = {};
    const providers: InstanceWrapper[] = this.discoveryService
      .getProviders()
      .filter((wrapper: InstanceWrapper) =>
        this.metadataAccessor.isQueueComponent(wrapper.metatype),
      );

    providers.forEach((wrapper: InstanceWrapper) => {
      const { instance, metatype } = wrapper;
      const isRequestScoped = !wrapper.isDependencyTreeStatic();
      const { name: queueName } =
        this.metadataAccessor.getQueueComponentMetadata(metatype);

      const queueToken = getQueueToken(queueName);
      const bullQueue = this.getQueue(queueToken, queueName);

      /**
       * TODO: BullMQ splits work into queues/workers. In order to handle
       * events we need to put the listener on the worker and not the queue.
       * The iteration below first grabs all processors and creates workers.
       * We want to ensure that all workers are created before we attempt to
       * attach any listener functions to them.
       * The second iteration through providers attaches event listeners for
       * each worker. There's probably a better way to do this.
       */
      // First iteration: create all provider workers
      this.metadataScanner.scanFromPrototype(
        instance,
        Object.getPrototypeOf(instance),
        (key: string) => {
          if (this.metadataAccessor.isProcessor(instance[key])) {
            const metadata = this.metadataAccessor.getProcessMetadata(
              instance[key],
            );
            workers[`${bullQueue.name}:::${metadata.name || '*'}`] =
              this.handleProcessor(
                instance,
                key,
                bullQueue,
                wrapper.host,
                isRequestScoped,
                metadata,
              );
          } else if (this.metadataAccessor.isGlobalListener(instance[key])) {
            const metadata = this.metadataAccessor.getGlobalListenerMetadata(
              instance[key],
            );
            const keyName = `${bullQueue.name}:::${metadata.name || '*'}`;

            // Only create one instance of queue events
            if (!(keyName in queueEvents)) {
              queueEvents[`${bullQueue.name}:::${metadata.name || '*'}`] =
                new QueueEvents(bullQueue.name, bullQueue.opts);
            }
          }
        },
      );

      // Second iteration: attach listener functions to workers created above.
      this.metadataScanner.scanFromPrototype(
        instance,
        Object.getPrototypeOf(instance),
        (key: string) => {
          if (this.metadataAccessor.isListener(instance[key])) {
            const metadata = this.metadataAccessor.getListenerMetadata(
              instance[key],
            );
            this.handleListener(
              instance,
              key,
              wrapper,
              bullQueue,
              workers[`${bullQueue.name}:::${metadata.name || '*'}`],
              metadata,
            );
          } else if (this.metadataAccessor.isGlobalListener(instance[key])) {
            const metadata = this.metadataAccessor.getGlobalListenerMetadata(
              instance[key],
            );
            this.handleListener(
              instance,
              key,
              wrapper,
              bullQueue,
              queueEvents[`${bullQueue.name}:::${metadata.name || '*'}`],
              metadata,
            );
          }
        },
      );
    });
  }

  getQueue(queueToken: string, queueName: string): Queue {
    try {
      return this.moduleRef.get<Queue>(queueToken, { strict: false });
    } catch (err) {
      this.logger.error(NO_QUEUE_FOUND(queueName));
      throw err;
    }
  }

  handleProcessor(
    instance: object,
    key: string,
    queue: Queue,
    moduleRef: Module,
    isRequestScoped: boolean,
    options: ProcessOptions = {},
  ) {
    let processor: Processor<any, void, string>;

    if (isRequestScoped) {
      processor = async (...args: unknown[]) => {
        const contextId = createContextId();

        if (this.moduleRef.registerRequestByContextId) {
          // Additional condition to prevent breaking changes in
          // applications that use @nestjs/bull older than v7.4.0.
          const jobRef = args[0];
          this.moduleRef.registerRequestByContextId(jobRef, contextId);
        }

        const contextInstance = await this.injector.loadPerContext(
          instance,
          moduleRef,
          moduleRef.providers,
          contextId,
        );
        return contextInstance[key].call(contextInstance, ...args);
      };
    } else {
      processor = instance[key].bind(instance);
    }

    return new Worker(
      queue.name,
      processor,
      Object.assign(queue.opts || {}, options),
    );
  }

  handleListener(
    instance: object,
    key: string,
    wrapper: InstanceWrapper,
    queue: Queue,
    listener: Worker | QueueEvents,
    options: BullQueueEventOptions,
  ) {
    if (!wrapper.isDependencyTreeStatic()) {
      this.logger.warn(
        `Warning! "${wrapper.name}" class is request-scoped and it defines an event listener ("${wrapper.name}#${key}"). Since event listeners cannot be registered on scoped providers, this handler will be ignored.`,
      );
      return;
    }
    if (options.name || options.id) {
      listener.on(
        options.eventName,
        async (jobOrJobId: Job | string, ...args: unknown[]) => {
          const job =
            typeof jobOrJobId === 'string'
              ? (await queue.getJob(jobOrJobId)) || { name: false, id: false }
              : jobOrJobId;

          if (job.name === options.name || job.id === options.id) {
            return instance[key].apply(instance, [job, ...args]);
          }
        },
      );
    } else {
      listener.on(options.eventName, instance[key].bind(instance));
    }
  }
}
Example #26
Source File: organizations.controller.ts    From nestjs-rest-microservices with MIT License 4 votes vote down vote up
@Controller('orgs')
export class OrganizationController implements OnModuleInit {
  constructor(@Inject('QueryUtils') private readonly queryUtils: QueryUtils, private readonly logger: PinoLogger) {
    logger.setContext(OrganizationController.name)
  }

  @Client(CommentsServiceClientOptions)
  private readonly commentsServiceClient: ClientGrpc

  @Client(OrganizationsServiceClientOptions)
  private readonly organizationsServiceClient: ClientGrpc

  @Client(UsersServiceClientOptions)
  private readonly usersServiceClient: ClientGrpc

  private commentsService: CommentsService

  private organizationsService: OrganizationsService

  private usersService: UsersService

  onModuleInit() {
    this.commentsService = this.commentsServiceClient.getService<CommentsService>('CommentsService')
    this.organizationsService = this.organizationsServiceClient.getService<OrganizationsService>('OrganizationsService')
    this.usersService = this.usersServiceClient.getService<UsersService>('UsersService')
  }

  @Get()
  @Header('Content-Type', 'application/json')
  async findOrganizations(@Query() query: RequestQuery): Promise<QueryResponse> {
    this.logger.info('OrganizationController#findOrganizations.call', query)

    const args = {
      ...(await this.queryUtils.getQueryParams(query))
    }

    const { count } = await this.organizationsService
      .count({
        where: !isEmpty(query.q) ? JSON.stringify({ name: { $like: query.q } }) : undefined
      })
      .toPromise()

    const data: OrganizationsQueryResult = await this.organizationsService
      .findAll({
        attributes: args.attributes,
        where: !isEmpty(query.q) ? JSON.stringify({ name: { $like: query.q } }) : undefined,
        order: JSON.stringify(args.order),
        offset: args.offset,
        limit: args.limit
      })
      .toPromise()

    const result: QueryResponse = {
      totalRecords: count,
      totalPages: Math.ceil(count / args.limit),
      page: args.page,
      limit: args.limit,
      ...data
    }

    this.logger.info('OrganizationController#findOrganizations.result', result)

    return result
  }

  @Get(':name/members')
  @Header('Content-Type', 'application/json')
  async findOrganizationMembers(@Param('name') name: string, @Query() query: RequestQuery): Promise<QueryResponse> {
    this.logger.info('OrganizationController#findOrganizationMembers.call', query)

    const organization: Organization = await this.organizationsService
      .findByName({
        name
      })
      .toPromise()

    if (!organization) throw new NotFoundException('NOT_FOUND', 'Organization not found.')

    const where = { organization: organization.id }

    if (!isEmpty(query.q)) {
      Object.assign(where, {
        name: { $like: query.q }
      })
    }

    const args = {
      ...(await this.queryUtils.getQueryParams(query))
    }

    const { count } = await this.usersService
      .count({
        where: JSON.stringify(where)
      })
      .toPromise()

    const data: UsersQueryResult = await this.usersService
      .findAll({
        attributes: args.attributes,
        where: JSON.stringify(where),
        order: !isEmpty(args.order) ? JSON.stringify(args.order) : JSON.stringify([['followers', 'DESC']]),
        offset: args.offset,
        limit: args.limit
      })
      .toPromise()

    const result: QueryResponse = {
      totalRecords: count,
      totalPages: Math.ceil(count / args.limit),
      page: args.page,
      limit: args.limit,
      ...data
    }

    this.logger.info('OrganizationController#findOrganizationMembers.result', result)

    return result
  }

  @Get(':name/comments')
  @Header('Content-Type', 'application/json')
  async findOrganizationComments(@Param('name') name: string, @Query() query: RequestQuery): Promise<QueryResponse> {
    this.logger.info('OrganizationController#findOrganizationComments.call', query)

    const organization: Organization = await this.organizationsService
      .findByName({
        name
      })
      .toPromise()

    if (!organization) throw new NotFoundException('NOT_FOUND', 'Organization not found.')

    const where = { organization: organization.id }

    if (!isEmpty(query.q)) {
      Object.assign(where, {
        name: { $like: query.q }
      })
    }

    const args = {
      ...(await this.queryUtils.getQueryParams(query))
    }

    const { count } = await this.commentsService
      .count({
        where: JSON.stringify(where)
      })
      .toPromise()

    const data: CommentsQueryResult = await this.commentsService
      .findAll({
        attributes: args.attributes,
        where: JSON.stringify(where),
        order: JSON.stringify(args.order),
        offset: args.offset,
        limit: args.limit
      })
      .toPromise()

    const result: QueryResponse = {
      totalRecords: count,
      totalPages: Math.ceil(count / args.limit),
      page: args.page,
      limit: args.limit,
      ...data
    }

    this.logger.info('OrganizationController#findOrganizationComments.call', result)

    return result
  }

  @Post(':name/comments')
  @Header('Content-Type', 'application/json')
  async createOrganizationComment(@Param('name') name: string, @Body() comment: CommentDto): Promise<Comment> {
    this.logger.info('OrganizationController#createOrganizationComment.call', name)

    const organization: Organization = await this.organizationsService
      .findByName({
        name
      })
      .toPromise()

    if (!organization) throw new NotFoundException('NOT_FOUND', 'Organization not found.')

    const result: Comment = await this.commentsService
      .create({
        ...comment,
        organization: organization.id
      })
      .toPromise()

    this.logger.info('OrganizationController#createOrganizationComment.result', result)

    return result
  }

  @Delete(':name/comments')
  @Header('Content-Type', 'application/json')
  async deleteOrganizationComments(@Param('name') name: string): Promise<Count> {
    this.logger.info('OrganizationController#deleteOrganizationComments.call', name)

    const organization: Organization = await this.organizationsService
      .findByName({
        name
      })
      .toPromise()

    if (!organization) throw new NotFoundException('NOT_FOUND', 'Organization not found.')

    const result: Count = await this.commentsService
      .destroy({
        where: JSON.stringify({ organization: organization.id })
      })
      .toPromise()

    this.logger.info('OrganizationController#deleteOrganizationComments.result', result)

    return result
  }
}
Example #27
Source File: admin.module.ts    From adminjs-nestjs with MIT License 4 votes vote down vote up
/**
 * Nest module which is responsible for an AdminJS integration
 * 
 * @summary Nest Module
 * 
 * @class
 * @name module:@adminjs/nestjs~AdminModule
 * @alias AdminModule
 * @memberof module:@adminjs/nestjs
 */
// This is needed by JSDoc which cannot parse this statement
@Module({})
export class AdminModule implements OnModuleInit {
  constructor(
    private readonly httpAdapterHost: HttpAdapterHost,
    private readonly loader: AbstractLoader,
    @Inject(CONFIG_TOKEN)
    private readonly adminModuleOptions: AdminModuleOptions,
  ) {}

  /**
   * Creates admin in a synchronous way
   * 
   * @param {AdminModuleOptions} options
   * @memberof module:@adminjs/nestjs~AdminModule
   * @method
   * @name createAdmin
   * @example
   * import { Module } from '@nestjs/common';
   * import { AdminModule } from '@adminjs/nestjs';
   * 
   * \@Module({
   *   imports: [
   *     AdminModule.createAdmin({
   *        rootPath: '/admin',
   *        resources: [],
   *     }),
   *   ],
   * })
   * export class AppModule {}
   * 
   */
  // This is needed by JSDoc which cannot parse this statement
  public static createAdmin(options: AdminModuleOptions & CustomLoader): DynamicModule {
    return {
      module: AdminModule,
      providers: [
        {
          provide: CONFIG_TOKEN,
          useValue: options,
        },
        options.customLoader ? {
          provide: AbstractLoader,
          useClass: options.customLoader,
        } : serveStaticProvider,
      ],
    }
  }
  
  /**
   * Creates admin in an asynchronous way
   * 
   * @param {AdminModuleFactory} options
   * @memberof module:@adminjs/nestjs~AdminModule
   * @method
   * @name createAdminAsync
   * @example
   * \@Module({
   *   imports: [
   *     MongooseModule.forRoot('mongodb://localhost:27017/test'),
   *     AdminModule.createAdminAsync({
   *       imports: [
   *         MongooseSchemasModule, // importing module that exported model we want to inject
   *       ],
   *       inject: [
   *         getModelToken('Admin'), // using mongoose function to inject dependency
   *       ],
   *       useFactory: (adminModel: Model<Admin>) => ({ // injected dependecy will appear as an argument
   *         adminJsOptions: {
   *           rootPath: '/admin',
   *           resources: [
   *             { resource: adminModel },
   *           ],
   *         },
   *       }),
   *     }),
   *     MongooseSchemasModule,
   *     ],
   *  })
   *  export class AppModule { }
   */
  public static createAdminAsync(options: AdminModuleFactory & CustomLoader): DynamicModule {
    return {
      imports: options.imports,
      module: AdminModule,
      providers: [
        {
          provide: CONFIG_TOKEN,
          useFactory: options.useFactory,
          inject: options.inject,
        },
        options.customLoader ? {
          provide: AbstractLoader,
          useClass: options.customLoader,
        } : serveStaticProvider,
      ],
    }
  }

  /**
   * Applies given options to AdminJS and initializes it
   */
  public onModuleInit() {
    if ('shouldBeInitialized' in this.adminModuleOptions && !this.adminModuleOptions.shouldBeInitialized) {
      return;
    }

    const admin = new AdminJS(this.adminModuleOptions.adminJsOptions);

    const { httpAdapter } = this.httpAdapterHost;
    this.loader.register(admin, httpAdapter, { 
      ...this.adminModuleOptions, 
      adminJsOptions: admin.options,
    });
  }
}
Example #28
Source File: event-store.service.ts    From nestjs-geteventstore with MIT License 4 votes vote down vote up
@Injectable()
export class EventStoreService
  implements OnModuleInit, OnModuleDestroy, IEventStoreService
{
  private logger: Logger = new Logger(this.constructor.name);
  private persistentSubscriptions: PersistentSubscription[];

  private isOnError = false;
  private isTryingToConnect = true;
  private isTryingToWriteEvents = false;
  private isTryingToWriteMetadatas = false;

  private connectionRetryFallback: Timeout;
  constructor(
    @Inject(EVENT_STORE_CONNECTOR)
    private readonly eventStore: Client,
    @Inject(EVENT_STORE_SUBSYSTEMS)
    private readonly subsystems: IEventStoreSubsystems,
    @Inject(EVENTS_AND_METADATAS_STACKER)
    private readonly eventsStacker: IEventsAndMetadatasStacker,
    private readonly eventStoreHealthIndicator: EventStoreHealthIndicator,
    @Optional() private readonly eventBus?: ReadEventBus,
  ) {}

  public async onModuleInit(): Promise<void> {
    return await this.connect();
  }

  public onModuleDestroy(): void {
    clearTimeout(this.connectionRetryFallback);
  }

  private async connect(): Promise<void> {
    try {
      if (this.subsystems.subscriptions)
        this.persistentSubscriptions =
          await this.subscribeToPersistentSubscriptions(
            this.subsystems.subscriptions.persistent,
          );
      if (this.subsystems.projections)
        await this.upsertProjections(this.subsystems.projections).catch((e) =>
          this.logger.error(e),
        );

      this.isOnError = false;
      this.isTryingToConnect = false;
      this.logger.log(`EventStore connected`);
      this.eventStoreHealthIndicator.updateStatus({
        connection: 'up',
        subscriptions: 'up',
      });
      await this.tryToWriteStackedEventBatches();
      await this.tryToWriteStackedMetadatas();
    } catch (e) {
      this.isTryingToConnect = true;
      this.eventStoreHealthIndicator.updateStatus({
        connection: 'down',
        subscriptions: 'down',
      });
      await this.retryToConnect();
    }
  }

  private async retryToConnect(): Promise<void> {
    this.logger.log(`EventStore connection failed : trying to reconnect`);
    this.connectionRetryFallback = setTimeout(
      async () => await this.connect(),
      RECONNECTION_TRY_DELAY_IN_MS,
    );
  }

  public async createProjection(
    query: string,
    type: 'oneTime' | 'continuous' | 'transient',
    projectionName?: string,
    options?:
      | CreateContinuousProjectionOptions
      | CreateTransientProjectionOptions
      | CreateOneTimeProjectionOptions,
  ): Promise<void> {
    switch (type) {
      case 'continuous':
        await this.eventStore.createContinuousProjection(
          projectionName,
          query,
          options ?? {},
        );
        break;
      case 'transient':
        await this.eventStore.createTransientProjection(
          projectionName,
          query,
          options ?? {},
        );
        break;
      case 'oneTime': {
        await this.eventStore.createOneTimeProjection(query, options ?? {});
        break;
      }
      default:
        return;
    }
  }

  public getProjectionState<T>(
    streamName: string,
    options?: GetProjectionStateOptions,
  ): Promise<T> {
    return this.eventStore.getProjectionState<T>(streamName, options);
  }

  public async updateProjection(
    projection: EventStoreProjection,
    content: string,
  ): Promise<void> {
    await this.eventStore.updateProjection(projection.name, content, {
      trackEmittedStreams: projection.trackEmittedStreams,
    });
  }

  private extractProjectionContent(projection: EventStoreProjection): string {
    let content;
    if (projection.content) {
      this.logger.log(`"${projection.name}" projection in content`);
      content = projection.content;
    } else if (projection.file) {
      this.logger.log(`"${projection.name}" projection in file`);
      content = readFileSync(projection.file, 'utf8');
    }
    return content;
  }

  public async upsertProjections(
    projections: EventStoreProjection[],
  ): Promise<void> {
    for (const projection of projections) {
      this.logger.log(`Upserting projection "${projection.name}"...`);

      const content = this.extractProjectionContent(projection);
      await this.upsertProjection(content, projection);

      this.logger.log(`Projection "${projection.name}" upserted !`);
    }
  }

  private async upsertProjection(
    content: string,
    projection: EventStoreProjection,
  ): Promise<void> {
    await this.createProjection(
      content ?? projection.content,
      projection.mode,
      projection.name,
      {
        trackEmittedStreams: projection.trackEmittedStreams,
      },
    ).catch(async (e) => {
      if (EventStoreService.isNotAProjectionAlreadyExistsError(e)) {
        throw Error(e);
      }
      await this.updateProjection(projection, content);
    });
  }

  public async createPersistentSubscription(
    streamName: string,
    groupName: string,
    settings: Partial<PersistentSubscriptionSettings>,
    options?: BaseOptions,
  ): Promise<void> {
    try {
      await this.eventStore.createPersistentSubscription(
        streamName,
        groupName,
        {
          ...persistentSubscriptionSettingsFromDefaults(),
          ...settings,
        },
        options,
      );
    } catch (e) {
      this.logger.error(e);
    }
  }

  public async updatePersistentSubscription(
    streamName: string,
    group: string,
    options: Partial<PersistentSubscriptionSettings>,
    credentials?: Credentials,
  ): Promise<void> {
    try {
      await this.eventStore.updatePersistentSubscription(
        streamName,
        group,
        {
          ...persistentSubscriptionSettingsFromDefaults(),
          ...options,
        } as PersistentSubscriptionSettings,
        { credentials },
      );
    } catch (e) {
      this.logger.error(e);
    }
  }

  public async deletePersistentSubscription(
    streamName: string,
    groupName: string,
    options?: DeletePersistentSubscriptionOptions,
  ): Promise<void> {
    try {
      await this.eventStore.deletePersistentSubscription(
        streamName,
        groupName,
        options,
      );
    } catch (e) {
      this.logger.error(`Error while deleting persistent subscription`);
      this.subsystems.onConnectionFail(e);
    }
  }

  public async subscribeToPersistentSubscriptions(
    subscriptions: IPersistentSubscriptionConfig[] = [],
  ): Promise<PersistentSubscription[]> {
    await this.upsertPersistentSubscriptions(subscriptions);

    return Promise.all(
      subscriptions.map(
        (config: IPersistentSubscriptionConfig): PersistentSubscription => {
          this.logger.log(
            `Connecting to persistent subscription "${config.group}" on stream "${config.stream}"...`,
          );
          const onEvent = (subscription, payload) => {
            return this.subsystems.onEvent
              ? this.subsystems.onEvent(subscription, payload)
              : this.onEvent(subscription, payload);
          };
          const persistentSubscription: PersistentSubscription =
            this.eventStore.connectToPersistentSubscription(
              config.stream,
              config.group,
            );
          if (!isNil(onEvent)) {
            persistentSubscription.on('data', (subscription, payload) => {
              onEvent(subscription, payload);
            });
          }
          if (!isNil(config.onSubscriptionStart)) {
            persistentSubscription.on(
              'confirmation',
              config.onSubscriptionStart,
            );
          }
          if (!isNil(config.onSubscriptionDropped)) {
            persistentSubscription.on('close', config.onSubscriptionDropped);
          }

          persistentSubscription.on('error', config.onError);

          persistentSubscription.on('error', async (): Promise<void> => {
            this.eventStoreHealthIndicator.updateStatus({
              subscriptions: 'down',
            });
            if (!this.isTryingToConnect) await this.connect();
          });
          this.logger.log(
            `Connected to persistent subscription "${config.group}" on stream "${config.stream}" !`,
          );
          return persistentSubscription;
        },
      ),
    );
  }

  private async upsertPersistentSubscriptions(
    subscriptions: IPersistentSubscriptionConfig[],
  ): Promise<void> {
    for (const subscription of subscriptions) {
      await this.upsertPersistentSubscription(subscription);
    }
  }

  private async upsertPersistentSubscription(
    subscription: IPersistentSubscriptionConfig,
  ): Promise<void> {
    try {
      await this.eventStore.createPersistentSubscription(
        subscription.stream,
        subscription.group,
        {
          ...persistentSubscriptionSettingsFromDefaults(),
          ...subscription.settingsForCreation?.subscriptionSettings,
        },
        subscription.settingsForCreation?.baseOptions,
      );
      this.logger.log(
        `Persistent subscription "${subscription.group}" on stream ${subscription.stream} created.`,
      );
    } catch (e) {
      if (EventStoreService.isNotAlreadyExistsError(e)) {
        this.logger.error('Subscription creation try : ', e);
        throw new Error(e);
      }
      await this.eventStore.updatePersistentSubscription(
        subscription.stream,
        subscription.group,
        {
          ...persistentSubscriptionSettingsFromDefaults(),
          ...subscription.settingsForCreation.subscriptionSettings,
        },
        subscription.settingsForCreation.baseOptions,
      );
    }
  }

  private static isNotAlreadyExistsError(e) {
    return e.code !== PERSISTENT_SUBSCRIPTION_ALREADY_EXIST_ERROR_CODE;
  }

  private static isNotAProjectionAlreadyExistsError(e): boolean {
    return e.code !== PROJECTION_ALREADY_EXIST_ERROR_CODE;
  }

  public getPersistentSubscriptions(): PersistentSubscription[] {
    return this.persistentSubscriptions;
  }

  public readMetadata(stream: string): Promise<GetStreamMetadataResult> {
    try {
      return this.eventStore.getStreamMetadata(stream);
    } catch (e) {
      this.logger.error(`Error while reading metadatas of stream ${stream}`);
      this.subsystems.onConnectionFail(e);
    }
  }

  public async writeMetadata(
    streamName: string,
    metadata: StreamMetadata,
    options?: SetStreamMetadataOptions,
  ): Promise<AppendResult> {
    this.eventsStacker.putMetadatasInWaitingLine({
      streamName,
      metadata,
      options,
    });

    return this.isTryingToWriteMetadatas
      ? null
      : await this.tryToWriteStackedMetadatas();
  }

  private async tryToWriteStackedMetadatas(): Promise<null | AppendResult> {
    try {
      this.isTryingToWriteMetadatas = true;
      let lastValidAppendResult: AppendResult = null;
      while (this.eventsStacker.getMetadatasWaitingLineLength() > 0) {
        const metadata: MetadatasContextDatas =
          this.eventsStacker.getFirstOutFromMetadatasWaitingLine();
        lastValidAppendResult = await this.eventStore.setStreamMetadata(
          metadata.streamName,
          metadata.metadata,
          metadata.options,
        );
        this.eventsStacker.shiftMetadatasFromWaitingLine();
      }
      this.isTryingToWriteMetadatas = false;
      return lastValidAppendResult;
    } catch (e) {
      this.eventStoreHealthIndicator.updateStatus({ connection: 'down' });
      this.subsystems.onConnectionFail(e);
      return null;
    }
  }

  public async readFromStream(
    stream: string,
    options?: ReadStreamOptions,
    readableOptions?: ReadableOptions,
  ): Promise<StreamingRead<ResolvedEvent>> {
    try {
      return this.eventStore.readStream(stream, options, readableOptions);
    } catch (e) {
      this.logger.error(`Error while reading a stream`);
      this.subsystems.onConnectionFail(e);
    }
  }

  public async writeEvents(
    stream: string,
    events: EventData[],
    expectedVersion: AppendToStreamOptions = {
      expectedRevision: constants.ANY,
    },
  ): Promise<AppendResult> {
    this.eventsStacker.putEventsInWaitingLine({
      events,
      stream,
      expectedVersion,
    });
    return this.isTryingToWriteEvents
      ? null
      : await this.tryToWriteStackedEventBatches();
  }

  private async tryToWriteStackedEventBatches(): Promise<AppendResult> {
    try {
      let lastValidAppendResult: AppendResult = null;
      this.isTryingToWriteEvents = true;

      while (this.eventsStacker.getEventBatchesWaitingLineLength() > 0) {
        lastValidAppendResult = await this.tryToWriteEventsFromBatch();
      }

      this.isTryingToWriteEvents = false;
      return lastValidAppendResult;
    } catch (e) {
      this.eventStoreHealthIndicator.updateStatus({ connection: 'down' });
      this.subsystems.onConnectionFail(e);
      return null;
    }
  }

  private async tryToWriteEventsFromBatch(): Promise<null | AppendResult> {
    const batch: EventBatch =
      this.eventsStacker.getFirstOutFromEventsBatchesWaitingLine();
    const appendResult: AppendResult = await this.eventStore.appendToStream(
      batch.stream,
      batch.events,
      batch.expectedVersion,
    );
    this.eventsStacker.shiftEventsBatchFromWaitingLine();
    return appendResult;
  }

  private async onEvent(
    subscription: IPersistentSubscriptionConfig,
    payload,
  ): Promise<unknown> {
    return EventHandlerHelper.onEvent(
      this.logger,
      subscription,
      payload,
      this.eventBus,
    );
  }
}