Balbuti #2: Aktualizacja dynamicznych endpointów

in blurt-179874 •  6 months ago 

Tak jak wspomniałem w poprzednim poście, pewne braki w API RPC skłoniły mnie do stworzenia własnego BE. Mógłbym pewnie napisać go znacznie szybciej, gdyby nie to, że chciałbym go wykorzystać w przyszłości do kolejnych serwisów nie modyfikując go zbytnio. Rdzeniem aplikacji będzie plik konfiguracyjny, który będzie elastyczny i pozwoli realizować różne funkcjonalności, chociaż domyślam się, że jakieś bardziej skomplikowane rzeczy będą musiały być implementowane w TS.

Ostatnim razem zaimplementowałem obsługę JSONata do parsowania bloków przychodzących, a następnie wsadzanie tych danych prosto do bazy. Tym razem zacząłem implementację pobierania danych z bazy. Oczywiście zdecydowałem się wcześniej na użycie Nest.JS i swaggera aby dało się w łatwy sposób sprawdzić konfigurację z poziomu strony. No i już na start pojawił się mały problem. Dekoratory, które są używane przez swaggera modyfikują odpowiednio metadane klasy tak aby potem w odpowiedni sposób pokazać wszystko na stronie. Wymyśliłem, że stworzę swój dekorator, który będzie wołał kolejne dekoratory swaggera i nest.js z parametrami, które są przekazywane przez plik konfiguracyjny.


const endpoints = config.get('api') ?? {};

function ConfigEndpoints(name: string) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor,
  ) {
    const endpoint = endpoints[name];
    const originalMethod = descriptor.value;

    for (const path in endpoint) {
      // Add a new method to the class
      const func = function (type: string, ...args: any[]) {
        console.log(type, ...args);
        // Call the original method with the first argument set to the method name
        return originalMethod.apply(this, [path, type, ...args]);
      };
      const desc = {
        value: func,
        writable: false,
        enumerable: false,
        configurable: false,
      };
      Object.defineProperty(target, `${name}_${path}`, desc);
      // Decorate the original method with @Get
      Get(`/${path}`)(target, `${name}_${path}`, desc);

      const settings = endpoint[path];
      for (const param in settings.params) {
        switch (settings.params[param]) {
          // FIXME: Can be more sexy
          case 'number':
            Bind(Param(param))(target, `${name}_${path}`, desc);
            ApiParam({ name: param, type: Number })(
              target,
              `${name}_${path}`,
              desc,
            );
            break;
          case 'string':
          default:
            Bind(Param(param))(target, `${name}_${path}`, desc);
            ApiParam({ name: param, type: String })(
              target,
              `${name}_${path}`,
              desc,
            );
        }
      }
    }
    return descriptor;
  };
}

@Controller('cherpilo')
export class CherpiloController {
  constructor(private readonly service: CherpiloService) {}

  @ConfigEndpoints('posts')
  posts(path: string, type: string, ...args: any[]): boolean {
    return this.service.posts(path, type, ...args);
  }
}

Dzięki temu taka konfiguracja:

api:
  posts:
    posts/tag/:name:
      params:
        name: string
    posts/user/:name:
      params:
        name: string

zamienia się w coś tego typu:

image.png

No nic teraz zostało jeszcze stworzenie jakiejś dynamicznej projekcji, która z bazy wyciągnie odpowiednie dane.

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE BLURT!