Bolt.jsโšก + Firebase๐Ÿ”ฅ๊ธฐ์ˆ  ํˆฌ๊ณ ์˜ ์ง€ํ‘œ๋ฅผ ์ž˜ ์ง‘๊ณ„ํ•  ์ˆ˜ ์žˆ๋Š” Slack Bot์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ธ€์€ Slack Advent Calendar 2020 16์ผ์งธ ๋˜๋Š” ๊ธ€์ด๋‹ค.
Slack์— ์ ์šฉ๋˜๋Š” ํ”„๋ ˆ์ž„์˜ Bolt์ž…๋‹ˆ๋‹ค.js์™€Firebase๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž์‹ ์ด ์ถœ๋ ฅํ•œ ์ง€ํ‘œ๋ฅผ ํ•ฉ์‚ฐํ•˜์—ฌ ์ข‹์€ ๋Š๋‚Œ์œผ๋กœ ์ถœ๋ ฅ์„ ํฌ๋งทํ•  ์ˆ˜ ์žˆ๋Š” Bot๋ฅผ ๋งŒ๋“ค์–ด ๋ดค์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์†Œ๊ฐœํ•ด ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

๋งŒ๋“  ๋ฌผ๊ฑด


์ž์‹ ์˜ ๊ธฐ์ˆ ๋กœ ํˆฌ๊ณ ํ•œ ๊ณ„์ •๋ช…์„ ์ž…๋ ฅํ•˜๋ฉด ์ด๋Ÿฐ ๋Š๋‚Œ์—์„œ ์ง€ํ‘œ๋ฅผ ํ•ฉ์‚ฐํ•ด ๋ฐ˜๋‚ฉํ•˜๋Š” ์Šฌ๋ž™๋ด‡.

๊ธฐ๋Šฅ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
  • /report ๋ช…๋ น์„ ์ž…๋ ฅํ•˜๋ฉด ์ž…๋ ฅ ๋ชจ๋“œ๊ฐ€ ์—ด๋ฆฝ๋‹ˆ๋‹ค.
  • ํŠธ์œ„ํ„ฐ, Qita, Zenn, note์˜ ๊ณ„์ • ์ด๋ฆ„ + ์ฃผ์„
  • ์„ ๋ชจ๋“œ๋กœ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ฐœ์–ธ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ ํ›„ ์ž…๋ ฅ ๋‚ด์šฉ์— ๋”ฐ๋ผ ๊ฐ ์ง€ํ‘œ๋ฅผ ํ†ต๊ณ„ํ•˜์—ฌ ๊ฒฐ๊ณผ๋ฅผ ์ฑ„๋„์— ํˆฌ๊ณ 
  • ๋Œ€์‘ํ•˜๋Š” ์„œ๋น„์Šค๋Š” ์ด์ชฝ์ž…๋‹ˆ๋‹ค.
    ์„œ๋น„์Šค
    ์ง€ํ‘œ
    Twitter
    ํŠธ์œ„ํ„ฐ ์ˆ˜, ํŒ”๋กœ์›Œ ์ˆ˜, ํŒ”๋กœ์›Œ ์ˆ˜
    Qiita
    ๊ธฐ์‚ฌ ์ˆ˜, LGTM ์ˆ˜, ๊ด€์‹ฌ ๋ถ„์•ผ ์ˆ˜
    Zenn
    ๊ธฐ์‚ฌ ์ˆ˜, Like ์ˆ˜, ํŒ”๋กœ์›Œ ์ˆ˜
    note
    ๊ธฐ์‚ฌ ์ˆ˜, Like ์ˆ˜, ํŒ”๋กœ์›Œ ์ˆ˜
    ๋‚˜๋Š” ์–ด๋–ป๊ฒŒ ์‰ฝ๊ฒŒ ์ž…๋ ฅํ•˜๊ณ  ์–ด๋–ป๊ฒŒ ์ž์‹ ์˜ ์ง„์ „์„ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•˜๋Š”์ง€์— ์ฐฉ์•ˆํ–ˆ๋‹ค.
    ์ž…๋ ฅ์— ๊ด€ํ•ด์„œ, ๋ชจ๋“  ์Šฌ๋ž™ ๋กœ๊ทธ์•„์›ƒ์€ ๋งˆ์ง€๋ง‰์œผ๋กœ ์ž…๋ ฅํ•œ ๋‚ด์šฉ์„ Firestore์— ์ €์žฅํ•˜๊ณ , ์ฒซ ๋ฒˆ์งธ ํšŒ ์ดํ›„์—๋Š” ์ž…๋ ฅ ํ‘œ์‹œ์ค„์˜ ์ดˆ๊ธฐ ๊ฐ’์œผ๋กœ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.
    ์ง„์ „ ํŒŒ์•…์— ๊ด€ํ•ด์„œ๋Š” ์ดˆํšŒ ์ดํ›„์— ์ง€๋‚œ๋ฒˆ ์ง‘ํ–‰์‹œ์˜ ์ฐจ์ด๋ฅผ ๋‚˜ํƒ€๋‚ผ ๊ฒƒ์ด๋‹ค.๋”ฐ๋ผ์„œ ์ผ์ฃผ์ผ์— ํ•œ ๋ฒˆ์”ฉ ์ง€๋ น์„ ๋‚ด๋ฆฌ๋ฉด ์ง€๋‚œ์ฃผ์™€ ๊ฐ€๋ณ๊ฒŒ ๋น„๊ตํ•  ์ˆ˜ ์žˆ๋‹ค.

    ํ”„๋กœ๋น„์ €๋‹


    ์–ด๋–ค ๊ตฌ์„ฑ์ธ์ง€ ๋Œ€์ถฉ ์†Œ๊ฐœํ• ๊ฒŒ์š”.
    Bolt.Firebase for Cloud Function์œผ๋กœ js๋ฅผ ์ด๋™ํ•˜์—ฌ Slack๊ณผ ๋Œ€ํ™”ํ•ฉ๋‹ˆ๋‹ค.
    ๊ทธ๋Ÿฐ ๋‹ค์Œ ์–‘์‹์˜ ์ž…๋ ฅ ๋‚ด์šฉ, ์ง€ํ‘œ ๋ฐ์ดํ„ฐ๋ฅผ Firestore์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

    ์ง€ํ‘œ ํ†ต๊ณ„ ์„น์…˜์€ ์„œ๋น„์Šค๋ณ„ API ํฌ์ธํŠธ๋ฅผ ์ง์ ‘ ์ ์–ด ์‚ฌ์šฉํ–ˆ๋‹ค.
    Zenn API ํด๋ผ์ด์–ธํŠธ
    functions/src/lib/zennClient.ts
    import {
      ZennArticle,
      Follower,
      ZennMyArticlesResponse,
      ZennMyFollowersResponse,
    } from "../types/zennTypes";
    import axios from "axios";
    import { ApiClient, ZennIndex } from "../types/types";
    
    export class ZennClient implements ApiClient {
      private readonly BASE_API_URL = "https://api.zenn.dev";
    
      constructor(private userName: string) {
        axios.defaults.baseURL = this.BASE_API_URL;
      }
    
      async fetchIndex(): Promise<ZennIndex> {
        const articles = await this.fetchMyAllArticles();
        const followers = await this.fetchMyFollowers();
    
        return {
          postCount: articles.length,
          likeCount: this.tallyUpLikeCount(articles),
          followerCount: followers.length,
        };
      }
    
      private async fetchMyAllArticles(): Promise<ZennArticle[]> {
        const response = await axios.get<ZennMyArticlesResponse>(
          `/users/${this.userName}/articles`
        );
        return response.data.articles ?? [];
      }
    
      private async fetchMyFollowers(): Promise<Follower[]> {
        let followers = [] as Follower[];
        let hasNextPage = true;
    
        try {
          for (let page = 1; hasNextPage; page++) {
            const response = await axios.get<ZennMyFollowersResponse>(
              `/users/${this.userName}/followers?page=${page}`
            );
            hasNextPage = !!response.data.next_page;
            followers = [...followers, ...response.data.users];
          }
        } catch (e) {
          console.error(e);
        }
    
        return followers;
      }
    
      private tallyUpLikeCount(articles: ZennArticle[]): number {
        return articles.reduce<number>((count, article) => {
          return count + article.liked_count;
        }, 0);
      }
    }
    ```ใ€€
    
    Qiita์šฉ API ํด๋ผ์ด์–ธํŠธ
    functions/src/lib/qiitaClient.ts
    import * as functions from "firebase-functions";
    import axios from "axios";
    import { QiitaItem, QiitaUser } from "../types/qiitaTypes";
    import { ApiClient, QiitaIndex } from "../types/types";
    
    export class QiitaClient implements ApiClient {
      private readonly BASE_URL = "https://qiita.com/api/v2";
      private readonly PER_PAGE = 100;
    
      constructor(private userName: string) {
        axios.defaults.baseURL = this.BASE_URL;
        axios.defaults.headers["Authorization"] = `Bearer ${
          functions.config().token.qiita
        }`;
      }
    
      async fetchIndex(): Promise<QiitaIndex> {
        const user = await this.fetchUser();
        const items = await this.fetchAllItems(user);
        const lgtmCount = this.tallyUpLgtmCount(items);
    
        return {
          postCount: user.items_count ?? 0,
          lgtmCount: lgtmCount,
          followerCount: user.followers_count ?? 0,
        };
      }
    
      private async fetchUser() {
        const response = await axios.get<QiitaUser>(`/users/${this.userName}`);
        return response.data;
      }
    
      private async fetchAllItems(user: QiitaUser | null) {
        if (!user) {
          return [];
        }
        // ๆœ€ๅคงใƒšใƒผใ‚ธๆ•ฐ
        const maxPage = Math.ceil(user.items_count / this.PER_PAGE);
        // ๆŠ•็จฟไธ€่ฆงใฎๅ–ๅพ—
        let allItems = [] as QiitaItem[];
        await Promise.all(
          [...Array(maxPage).keys()].map(async (i) => {
            const items = await this.fetchItems(i + 1, this.PER_PAGE);
            allItems = [...allItems, ...items];
          })
        );
        return allItems;
      }
    
      private async fetchItems(page: number, perPage: number) {
        const response = await axios.get<QiitaItem[]>(
          `/items?page=${page}&per_page=${perPage}&query=user:${this.userName}`
        );
        return response.data;
      }
    
      private tallyUpLgtmCount(items: QiitaItem[]) {
        const lgtmCount = items.reduce(
          (result, item) => result + item.likes_count,
          0
        );
        return lgtmCount;
      }
    }
    
    te API ํด๋ผ์ด์–ธํŠธ ์—†์Œ
    functions/src/lib/noteClient.ts
    import axios from "axios";
    import {
      NoteContent,
      NoteContentsResponse,
      NoteUserResponse,
    } from "../types/noteTypes";
    import { ApiClient, NoteIndex } from "../types/types";
    
    export class NoteClient implements ApiClient {
      private readonly BASE_URL = "https://note.com/api/v2";
    
      constructor(private userName: string) {
        axios.defaults.baseURL = this.BASE_URL;
      }
    
      async fetchIndex(): Promise<NoteIndex> {
        const user = await this.fetchUser();
        const contents = await this.fetchAllContent();
    
        return {
          postCount: user.noteCount ?? 0,
          likeCount: this.tallyUpLikeCount(contents),
          followerCount: user.followerCount ?? 0,
        };
      }
    
      private async fetchUser() {
        const response = await axios.get<NoteUserResponse>(
          `/creators/${this.userName}`
        );
        return response.data.data;
      }
    
      private async fetchAllContent() {
        let contents = [] as NoteContent[];
        let isLastPage = false;
    
        try {
          for (let page = 1; !isLastPage; page++) {
            const responseData = await this.fetchContents(page);
            isLastPage = responseData.isLastPage;
            contents = [...contents, ...responseData.contents];
          }
        } catch (e) {
          console.log(e);
        }
    
        return contents;
      }
    
      private async fetchContents(page: number) {
        const response = await axios.get<NoteContentsResponse>(
          `/creators/${this.userName}/contents?kind=note&page=${page}`
        );
        return response.data.data;
      }
    
      private tallyUpLikeCount(contents: NoteContent[]) {
        const likeCount = contents.reduce(
          (result, content) => result + content.likeCount,
          0
        );
        return likeCount;
      }
    }
    
    Twitter API ํด๋ผ์ด์–ธํŠธ
    functions/src/lib/twitterClient.ts
    import axios from "axios";
    import { PublicMetrics, UsersResponse } from "../types/twitterTypes";
    import * as functions from "firebase-functions";
    import { ApiClient, TwitterIndex } from "../types/types";
    
    export class TwitterClient implements ApiClient {
      private readonly BASE_URL = "https://api.twitter.com/2";
    
      constructor(private userName: string) {
        axios.defaults.baseURL = this.BASE_URL;
        axios.defaults.headers["Authorization"] = `Bearer ${
          functions.config().token.twitter
        }`;
      }
    
      async fetchIndex(): Promise<TwitterIndex> {
        const metrics = await this.fetchUserMetrics();
    
        return {
          tweetCount: metrics.tweet_count,
          followersCount: metrics.followers_count,
          followingCount: metrics.following_count,
        };
      }
    
      private async fetchUserMetrics(): Promise<PublicMetrics> {
        const response = await axios.get<UsersResponse>(
          `/users/by/username/${this.userName}?user.fields=public_metrics`
        );
        return response.data.data.public_metrics;
      }
    }
    
    ์  , ๋…ธํŠธ๊ฐ€ ๊ณต์‹ API๋ฅผ ๊ณต๊ฐœํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ์ธํ„ฐ๋„ท ์ •๋ณด์—์„œ ๊ฒฝ๋กœ, ์‘๋‹ต์„ ๋ณด๊ณ  ๊ตฌ์ถ•ํ•œ๋‹ค.๊ฐ ์„œ๋น„์Šค์˜ ํ–ฅํ›„ ๊ฐœ์ •์€ ์ด์šฉํ•  ์ˆ˜ ์—†์„ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค.
    ์ฝ”๋“œ๋Š” ๋ชจ๋‘ ์ฐฝ๊ณ ์—์„œ ๊ณต๊ฐœ๋œ๋‹ค.
    API ํด๋ผ์ด์–ธํŠธ ์ƒ์„ธ ์ •๋ณด, Bolt.js์˜ ์„ค์น˜ ๋“ฑ์€ ์•„๋ž˜๋ฅผ ๋ณด์‹ญ์‹œ์˜ค.
    https://github.com/kawamataryo/blog-index

    ๋ง‰ํžŒ ๊ณณ์„ ์‹ค์žฅํ•˜๋‹ค


    ์ด๋ฒˆ ์Šฌ๋ž™๋ด‡ ์ž‘์—…์—์„œ ๋ฐ˜ํ•œ ์ ์ด ์žˆ๋Š”๋ฐ ์ œ๊ฐ€ ์†Œ๊ฐœํ•ด ๋“œ๋ฆด๊ฒŒ์š”.

    FaaS์—์„œ ๋ณผํŠธ.์งˆ๋ฌธ


    ์‹ค์ œ๋กœ ํด๋ผ์šฐ๋“œ ํŽ€์…˜ ํฌ ํŒŒ์ด์–ด๋ฒ ์ด์Šค์™€ AWS ๋žŒ๋ฐ”๋‹ค ๋“ฑ ํŒŒ์•„์Šค์—์„œ ๋ณผํŠธ.js์˜ ์Šฌ๋ž™๋ด‡ ๊ตฌ์„ฑ์— ๋”ฐ๋ผ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ œ์•ฝ์ด ์žˆ์–ด ์กฐ๊ธˆ ๊ณต์„ ๋“ค์—ฌ์•ผ ํ•œ๋‹ค.
  • (1) Slack์—์„œ HTTP ์š”์ฒญ์„ ๋ฐ›์œผ๋ฉด 3์ดˆ ์•ˆ์— HTTP ์‘๋‹ต
  • ์„ ๋˜๋Œ๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • (2) HTTP ์š”์ฒญ์„ ํ„ฐ์น˜ํ•œFaaS์— ์‹œ์ž‘ํ•˜๋ฉด ์ฒ˜๋ฆฌ ๋„์ค‘์— ์‘๋‹ต์„ ๋˜๋Œ๋ ค์ฃผ๋ฉด ํ›„์† ์ฒ˜๋ฆฌ์˜ ์‹คํ–‰์ด ๋ณด์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค
  • (1)์˜ 3์ดˆ ์•ˆ์— ์‘๋‹ต ๊ทœ์น™์˜ ์ œํ•œ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ณผํŠธ์ž…๋‹ˆ๋‹ค.js์—์„œ ํ•จ์ˆ˜ ack() ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.ack()๋Š” 200OK์˜ HTTP ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋กœ, ์ด ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋น„๋™๊ธฐ์ ์œผ๋กœ ํ›„์† ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    app.action('approve_button', async ({ ack, say }) => {
      // ใ“ใฎๆ™‚็‚นใงใƒฌใ‚นใƒใƒณใ‚นใฏ่ฟ”ใ•ใ‚Œใ‚‹
      await ack();
      // ไปฅ้™ใฏ้žๅŒๆœŸใงๅ‡ฆ็†ใ•ใ‚Œใ‚‹ใ€‚3็ง’ไปฅๅ†…ๅฟœ็ญ”ใฎๅˆถ็ด„ใฏใชใ„ใ€‚
      await superHeavyTask()
    });
    
    ๋‹จ, (2)์™€ ๊ฐ™์ดFaaS๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
    ์ง‘ํ–‰ack()ํ•˜๋Š” ์ˆœ๊ฐ„ ๊ทธ ํ™˜๊ฒฝ์˜ ์ฒ˜๋ฆฌ๊ฐ€ ์™„์„ฑ๋œ ๊ฒƒ์œผ๋กœ ์—ฌ๊ฒจ์ ธ ํ›„์† ์ฒ˜๋ฆฌ์˜ ์ง‘ํ–‰์„ ๋ณด์žฅํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
    ์ด ๋ณผํŠธ์— ๋Œ€์ฒ˜ํ•˜๊ธฐ ์œ„ํ•ด์„œjs ์ธก์—์„œ๋„ ack()์˜ ์‹คํ–‰์„ ์ฒ˜๋ฆฌ ์™„๋ฃŒ๋กœ ์ง€์—ฐ์‹œํ‚ค๋Š” ์˜ต์…˜processBeforeResponse์ด ์žˆ์ง€๋งŒ ์ด๊ฒƒ์„ ์„ค์ •ํ•˜๋”๋ผ๋„ (1) 3์ดˆ ์ด๋‚ด์˜ ๊ทœ์น™์„ ์ง€์ผœ์•ผ ํ•œ๋‹ค.
    ์ด๋ฒˆ Bot์€ ๋ชจ๋“œ๋กœ ์ˆ˜์‹ ๋œ ๊ฐ’์„ ์ด์šฉํ•˜์—ฌ ๊ฐ ์„œ๋น„์Šค์— ์ ‘๊ทผํ•œ API๋ฅผ ํ†ตํ•ด ๊ฒฐ๊ณผ๋ฅผ ์ง‘๊ณ„ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋„์ €ํžˆ 3์ดˆ ์ด๋‚ด์˜ ์‘๋‹ต ๊ทœ์น™์„ ์ง€ํ‚ค์ง€ ๋ชปํ•ด ์‹œ๊ฐ„ ์ดˆ๊ณผ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.
    ๋™๊ธฐํ™” ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋ฉด ์‹œ๊ฐ„ ์ดˆ๊ณผ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€๋งŒ Function ํ•จ์ˆ˜์—์„œ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰ํ•˜๋ฉดFaaS์˜ ๋””์ž์ธ์—์„œ ์‹คํ–‰์„ ๋ณด์žฅํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์–ด๋–ป๊ฒŒ ํ•ฉ๋‹ˆ๊นŒ?๐Ÿค”
    Bolt.js Pythhon ๋ฒ„์ „ bolt-python ์—์„œ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.์ž์„ธํ•œ ๋‚ด์šฉ์€ ์•„๋ž˜๋ฅผ ๋ณด์‹ญ์‹œ์˜ค.
    FaaS์—์„œ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•œ Bolt for Python์˜ ๊ณผ์ œ - Qita

    ํ•ด๊ฒฐ์ฑ…


    ์ด๋ฒˆ์—๋Š”Queue์„ฑ์œผ๋กœFirestore๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—Function ๋‹จ์œ„๋กœ ๋ชจ๋“œ์˜ ์‘๋‹ต๊ณผ ํ•ฉ๊ณ„ ์ฒ˜๋ฆฌ์˜ ์‹คํ–‰์„ ๋ถ„๋ฆฌํ•˜๋Š” ์ผ๋กœ ์ƒ์ˆ ํ•œ ๋ฌธ์ œ๋ฅผ ํšŒํ”ผํ•˜์˜€๋‹ค.
    ์ฒ˜๋ฆฌ์˜ ์‹คํ–‰ ์ ˆ์ฐจ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
    ์‚ฌ์„  ๋ช…๋ น ์‹คํ–‰์—์„œ ํ‘œ์‹œ ๋ชจ๋“œ๋กœ
  • ์Šฌ๋ผ์ด๋”ฉ ๋ช…๋ น ์‹คํ–‰
  • Firestore์—์„œ ์‚ฌ์šฉ์ž์˜ ๊ณผ๊ฑฐ ๋ฐœ์–ธ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์–ป๊ธฐ
  • ๊ณผ๊ฑฐ ํˆฌ๊ณ ๊ฐ€ ์žˆ์œผ๋ฉด ํ‘œ์˜ ์ดˆ๊ธฐ๊ฐ’
  • ์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ๋””์Šคํ”Œ๋ ˆ์ด ๋ชจ๋“œ

  • ๋ชจ๋“œ์—์„œ ์ง€ํ‘œ ํ†ต๊ณ„, ํ†ต๋กœ๋กœ ํˆฌ๊ณ 
  • ๋ชจ๋“œ์˜ ๋‚ด์šฉ ์ž…๋ ฅ/๋ฐœ์†ก
  • Firestore์— ๋ฐ์ดํ„ฐ ์ €์žฅ
  • 200 ์‘๋‹ต ๋ฐ˜ํ™˜ ๋ฐ ๋‹ซ๊ธฐ ๋ชจ๋“œ
  • onCreate์˜ ๊ฐˆ๊ณ ๋ฆฌ๋กœ ๋‹ค๋ฅธ ํ•จ์ˆ˜ ์‹œ์ž‘ํ•˜๊ธฐ
  • ํ•ฉ๊ณ„ ๊ฐ ์ง€ํ‘œ
  • ๋ฐœํ‘œ ๊ฒฐ๊ณผ

  • ํŠนํžˆ ์ค‘์š”ํ•œ ๊ฒƒ์€ ๋ชจ๋“œ์˜ ๋ฐœ์†ก๋ถ€ํ„ฐ ์‹œ์ž‘๋˜๋Š” ์ฒ˜๋ฆฌ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œFirestore์˜ ๋ฐ์ดํ„ฐ ์ €์žฅ->onCreate์˜ ์„œ๋กœ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ Function ๋‹จ์œ„๋กœ ๋ถ„๋ฆฌ ์ฒ˜๋ฆฌํ•˜์—ฌ ์ƒ๊ธฐ ์ œ์•ฝ์„ ํ”ผํ•ฉ๋‹ˆ๋‹ค.
    ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ง€ํ‘œ ํ†ต๊ณ„๊ฐ€ ์–ผ๋งˆ๋‚˜ ๊ฑธ๋ฆฌ๋“  ์ƒ๊ด€์—†์ด ๋ชจ๋“œ์— ๋Œ€ํ•œ ์‘๋‹ต์ด ์™„์„ฑ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์‹œ๊ฐ„์„ ์ดˆ๊ณผํ•˜์ง€ ์•Š๋Š”๋‹ค.

    ๋๋งบ๋‹ค


    ์ด์ƒ "Bolt.js"โšก + Firebase๐Ÿ”ฅโ€.
    ์ฐธ๊ฐ€ํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ์—”์ง€๋‹ˆ์–ด์™€ ์ธ์ƒ์˜ ์Šฌ๋ž™๋„ ์ด์šฉ์ด ๊ฐ€๋Šฅํ•ด ๊ธฐ์˜๋‹ค.
    ํ•„์š”ํ•˜์‹œ๋ฉด ๊ณต๊ฐœ ์•ฑ์„ ์‚ฌ์šฉํ•ด ๋ณด๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.๋ฐ˜์‘์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค๋ฉด ๋‚˜๋Š” ๋งค์šฐ ๊ธฐ์  ๊ฒƒ์ด๋‹ค.

    ์ฐธ๊ณ  ์ž๋ฃŒ

  • Slack | Bolt for JavaScript
  • FaaS์—์„œ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•œ Bolt for Python์˜ ๊ณผ์ œ - Qita
  • ์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ