NestJSでデコレーターをまとめて使い回す

f:id:cgig:20210107225703p:plain

はじめに

NestJSでコントローラーを作っていくと同じ用なデコレーターを定義することが多くなっていくことがあります

それをまとめてみました

日に日に増えていくデコレーター

@Controller('users')
@ApiTags('users')
@ApiBearerAuth()
@UseGuards(UserGuard)
@UseInterceptors(ClassSerializerInterceptor)
export class UserController {

このControllerはGuardがついてて…

とか一回立ち止まって考える時間が発生するのが嫌でした

デコレーターをまとめたデコレーターを作る

export const NullDecorator = (): ClassDecorator => () => {};

type Options = {
    path?: string;
    auth: boolean;
}

export function UserControllerDecorator(options?: Options): ClassDecorator {
    const { path, auth = false } = options ?? {};

    return applyDecorators(
        Controller(path ? `users/${path}` : 'users'),
        ApiTags('users'),
        ApiBearerAuth(),
        auth ? UseGuards(UserGuard) : NullDecorator(),
        UseInterceptors(ClassSerializerInterceptor),
    );
}

デコレーターを使う

@UserControllerDecorator()
export class UserController {}

@UserControllerDecorator({ auth: true, path: 'posts' })
export class UserPostController {}

終わりに

まとめられることを知って、まとめてみたらスッキリした気持ちになりました

まだ使いだして日が浅いので今後変更する可能性はありますが現状は上記のようにしています

Slackで見逃された投稿をスヌーズ機能で気づくようにする

はじめに

Slackに問い合わせが来た時に、複数人が対応するような体制だと「今は手が離せないから対応できないけど、誰かやってくれるやろ」ということが起きるかと思います。

たまに全員がこのマインドになった時に見逃されることがあります。

それを見逃さないようにしようという試みです。

仕様を決める

今回は下記のような仕様にしました

  • 特定のチャンネルの発言を全て対象とする
  • 特定の絵文字が指定されるまで通知する
  • スヌーズの間隔は常に10分とする

実装する

パッケージのインストール

SlackAPIを叩くためのpackageをインストールします

www.npmjs.com

SDKの初期化

トークンを使用してSDKを初期化します

const { WebClient } = require('@slack/web-api');

const web = new WebClient(token);

tokenはアプリをbotユーザーとして登録した時のtokenを使います botをチャンネルに追加しておきます f:id:cgig:20210105233524p:plain

特定のチャンネルのメッセージを取得する

今回はスヌーズ機能なので直近のメッセージさえあればいいので limit はデフォルトの 100 のままいきます

const response = await web.conversations.history({
    channel: 'CXXXXXXXX',
});

メッセージ一覧のテストはこちらから可能です api.slack.com

チャンネルIDの取得方法はこちら cgig.hatenablog.com

スヌーズ対象のメッセージを特定する

上記で定義した仕様に合致するメッセージを特定するための関数を作ります

const isRequiredAlert = (message, lastAlertTimestamp) => {
    const { subtype, reactions, ts } = message;

    // チャンネルに参加したときのメッセージは無視する
    if (subtype && subtype.startsWith('channel')) {
        return false;
    }

    // 特定のスタンプで反応があった時はスヌーズ対象から外す
    const stampNames = ['white_check_mark'];
    const reactionCount = (reactions ? reactions : []).filter((reaction) => stampNames.findIndex((name) => name ===reaction.name)).length;
    if (reactionCount > 0) {
        return false;
    }

    // Slackのタイムスタンプは秒なのでミリ秒に統一する
    const intervalMinutes = 10;
    const timestamp = lastAlertTimestamp ? lastAlertTimestamp : parseInt(ts, 10) * 1000;
    const now = Date.now();
    const diff = now - timestamp;

    // 前回の通知時間 or 投稿時間から10分経ってない場合は対象外
    if (diff < intervalMinutes * 60 * 1000) {
        return false;
    }

    return true;    
}

通知する

スヌーズ対象のメッセージが存在した場合はスレッドに返信します

await web.chat.postMessage({
    token: token,
    channel: channelId,
    text: `:alarm_clock:`,    // 目覚まし時計の絵文字でも送っておく
    thread_ts: message.ts,
});

// 何かしらで最後に通知した時間を保存しておく
await saveLastAlertTimestamp(message);

定期実行する

GCPAWSのサーバーレス環境を使用して1分間に1回動作するように設定すれば完了です

SlackのチャンネルIDを取得する

はじめに

Incoming Webhooksでメッセージを送るときや、 message historyを取得したい時などにチャンネルIDが必要になったので方法を記録しておきます

ブラウザで取得する

チャンネルIDを取得したいチャンネルを開きます

アドレスバーに表示されているURLのchannelIdの部分がチャンネルIDになります

C で始まってるのでわかりやすいと思います

https://<workspace>.slack.com/client/<teamId>/<channelId>

f:id:cgig:20210105234357p:plain

スレッドを開いている場合は /thread/<channelId>-<timestamp> が後ろに付くので注意

APIで取得する

APIで取得することも可能ですが上の方法の方が簡単だと思います

api.slack.com