사용자를 위한 콘텐츠 렌더링

이전 모듈에서는 Snyk App을 등록하고 권한 부여 흐름을 설정하며 앱 내에서 사용자 권한을 처리하는 방법을 다루었습니다. 이러한 주제들은 모두 모든 Snyk App의 기능에 대하여 중요하지만, 그것들은 모두 '보이지 않는' 주제라고 할 수 있습니다.

이번 모듈에서는 사용자에게 콘텐츠를 표시하는 데 초점을 맞춥니다. 특히, 권한이 부여된 사용자에게는 Snyk에서 가져온 프로젝트 목록을 표시하고, 권한이 부여되지 않은 사용자에게는 권한 부여를 할 수 있는 큰 버튼을 보여주고자 합니다.

Snyk App에 템플릿 엔진 추가

Express는 화면에 내용을 출력하고 HTML 서버 측에서 렌더링할 수 있지만, 템플릿 엔진을 사용하면 더욱 쉽게 작업할 수 있습니다. 이 튜토리얼에서는 EJS를 사용합니다.

먼저, 이 튜토리얼의 이 부분에서 필요한 노드 패키지를 설치하세요:

npm install --save ejs

다음으로, 이전 모듈에서 생성한initGlobalMiddlewares() 함수를 수정하여 express에게 EJS와 같은 _뷰 엔진_을 사용하고 뷰 템플릿을 저장할 위치를 알려줍니다. 우리는 EJS 템플릿을 ./src/views에 저장하고 이미지 및 CSS와 같은 일반적인 파일은 ./src/public에 저장할 것입니다.

먼저 새 디렉토리를 생성하세요.

mkdir -p ./src/views/partials
mkdir -p ./src/public

이제 ./src/app.ts를 업데이트할 수 있습니다:

// ./src/app.ts
...

class App {

  ...

  private initGlobalMiddlewares() {

    ...

    this.app.set("views", path.join(__dirname, "/views"));
    this.app.set("view engine", "ejs");
    this.app.use('/public', express.static(path.join(__dirname, '/public')));

    ...

  }

  ...

}

각 루트에 대해 템플릿을 제공할 때 해당 컨트롤러를 수정하고 res.render("<템플릿 이름>")을 사용하여 res.send()와 같이 더 간단한 것 대신에 사용하도록 해야 합니다.

예시:

...

private initRoutes() {
  this.router.get(`${this.path}`, this.indexPage);
}
private indexPage(req: Request, res: Response, next: NextFunction) {
  // 이 부분이 Express가 EJS 템플릿을 렌더링하도록 하는 부분입니다.
  return res.render("index");
}

...

이게 전부입니다.

EJS 템플릿은 부분 포함 개념을 지원합니다. 엄격하게 필요한 것은 아니지만, 루트 템플릿과는 구분을 위해 헤더 및 푸터와 같은 부분 템플릿을 저장할 ./src/views에 하위 디렉토리를 추가하는 것이 좋습니다. 이 튜토리얼에서는 ./src/views/partials를 사용하여 이러한 템플릿을 저장할 것입니다.

기본 EJS 템플릿

첫 번째로 생성할 템플릿은 부분 템플릿이며, 다른 템플릿에 포함될 것입니다. <head>에 속하는 스타일시트 및 기타 정보들을 링크하는 곳인 header.ejs입니다.

// ./views/partials/header.ejs

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet" />
    <link href="https://raw.githubusercontent.com/snyk/snyk-apps-demo/main/src/public/css/styles.css" />
    <link rel="shortcut icon" href="https://raw.githubusercontent.com/snyk/snyk-apps-demo/main/src/public/images/snyk_dog.svg" type="image/x-icon" />

    <title>Snyk Apps Tutorial</title>
  </head>
</html>

index.ejs 템플릿은 기본 / 루트를 다룰 것입니다.

// ./views/index.ejs

<%- include('./partials/header.ejs') %>

<body>
  <div class="index-page">
    <img class="index-page__snyk-logo" src="https://github.com/snyk/snyk-apps-demo/raw/main/src/public/images/snykLogoWithDog.svg" alt="snyk-logo" />
    <div class="index-page__card">
      <h1 class="index-page__title">Add Demo App</h1>
      <p class="index-page__description">Authorize this App to connect to your Snyk account.</p>
      <button class="button" onclick="location.href='/auth';">Install App</button>
    </div>
  </div>
</body>

callback.ejs는 성공적인 사용자 권한 부여에 대해 렌더링됩니다.

// ./views/callback.ejs

<%- include('./partials/header.ejs') %>
<body>
  <div>
    <h2 class="main__heading">Snyk Apps Tutorial</h2>
  </div>
  <div class="card__30">
    <div class="callback-page__success-box">
      <img class="snyk-con-img" src="https://github.com/snyk/snyk-apps-demo/raw/main/src/public/images/success_check.svg" alt="success" />
        <div>
          <h2 class="callback-page__success-text">Successfully connected to Snyk!</h2>
        </div>
        <button class="button" onclick="location.href='/projects';">List Projects</button>
      </div>
    </div>
  </div>
</body>

위의 템플릿들을 사용하여 새로운 루트를 만드는 경우 스스로 템플릿을 추가하기 시작하는 데 충분합니다. EJS를 계속 사용한다면 제공되는 기능에 대한 정보는 문서를 참조하세요.

Snyk 앱에 대한 콘텐츠를 렌더링하는 것은 당신이 원하는 만큼 간단하거나 복잡할 수 있습니다. JavaScript를 다루고 있기 때문에 선택사항이 매우 유연합니다!

사용자에게 프로젝트 목록 표시

기본 템플릿이 구성되었으므로, 사용자의 Snyk 데이터를 사용하여 Snyk 앱에 기능을 추가하는 방법을 살펴보겠습니다. 이 튜토리얼에서는 사용자가 Snyk 앱 내에서 Snyk의 모든 프로젝트를 볼 수 있도록 앱을 설정했습니다.

이것은 기본적이고 쉽게 확장 가능한 기능입니다.

우리는 다음을 만들어야 합니다:

  • 새로운 루트 컨트롤러

  • 프로젝트 데이터를 가져오는 기능(또는 기능들)

  • 프로젝트를 표시하는 EJS 템플릿

API 작업을 시작합니다. 이전 모듈에서 생성한 callSnykApi() 함수를 사용하여 진행합니다. 이것은 특정 루트와 직접 관련이 있기 때문에 해당 컨트롤러와 함께 이 파일을 저장할 것입니다. 이 튜토리얼 모듈 전체에서 사용한 패턴을 따라, 이 파일들을 모두 ./src/routes/projects/에 생성합니다.

// ./src/routes/projects/projectsHandler.ts

import { readFromDb } from "../../util/DB";
import { callSnykApi } from "../../util/apiHelpers";
import { EncryptDecrypt } from "../../util/encrypt-decrypt";
import { AuthData } from "../../interfaces/DB";
import { APIVersion } from "../../interfaces/API";
import { ENCRYPTION_SECRET } from "../../app";

/**
 * 사용자 프로젝트를 모두 가져오는 프로젝트 핸들러
 * 이는 사용자가 Snyk API에서 사용자 접근 토큰을 사용하여
 * 지정된 조직의 모든 프로젝트를 가져오는 예제 목적의 함수입니다.
 * 실제 제품에서는 토큰 스코프에 따라 액세스 가능한 내용이 달라집니다.
 * @returns 사용자 프로젝트 목록 또는 빈 배열
 */
export async function getProjectsFromApi(): Promise<unknown[]> {
  // DB에서 데이터 읽기
  const db = await readFromDb();
  const data = mostRecent(db.installs);
  // 데이터가 없으면 빈 배열 반환
  if (!data) return [];

  // 데이터(액세스 토큰) 복호화
  const eD = new EncryptDecrypt(ENCRYPTION_SECRET as string);
  const access_token = eD.decryptString(data?.access_token);
  const token_type = data?.token_type;
  // Snyk API v1에 대해 구성된 axios 인스턴스 호출
  const result = await callSnykApi(
    token_type,
    access_token,
    APIVersion.V1
  ).post(`/org/${data?.orgId}/projects`);

  return result.data.projects || [];
}

/**
 *
 * @param {AuthData[]} installs 설치 목록에서 최근 설치 가져오기
 * @returns 최근 설치 또는 없음
 */
export function mostRecent(installs: AuthData[]): AuthData | void {
  if (installs) {
    return installs[installs.length - 1];
  }
  return;
}

다음으로 루트 컨트롤러를 작성합니다. 패턴을 따라, ./src/routes/projects/projectsController.ts를 만듭니다.

// ./src/routes/projects/projectsController.ts

import type { Controller } from "../../interfaces/Controller";
import type { NextFunction, Request, Response } from "express";
import { Router } from "express";
import { getProjectsFromApi } from "./projectsHandler";

export class ProjectsController implements Controller {
  // 이 컨트롤러의 기본 URL 경로
  public path = "/projects";
  // 이 컨트롤러의 Express 라우터
  public router = Router();

  constructor() {
    this.initRoutes();
  }

  private initRoutes() {
    // 모든 사용자 프로젝트 목록을 렌더링하는 루트
    this.router.get(`${this.path}`, this.getProjects);
  }

  private async getProjects(req: Request, res: Response, next: NextFunction) {
    try {
      const projects = await getProjectsFromApi();
      return res.render("projects", {
        projects,
      });
    } catch (error) {
      return next(error);
    }
  }
}

새로운 루트 컨트롤러를 추가할 때마다 ./index.ts를 업데이트해야 합니다.

// ./src/index.ts

import IndexController from "./routes/index/indexController";
import AuthController from "./routes/auth/authController";
import CallbackController from "./routes/callback/callbackController";
import ProjectsController from "./routes/projects/projectsController";
import App from "./app";

new App([
   new IndexController(),
   new AuthController(),
   new CallbackController(),
   new ProjectsController()],
  3000
);

요약

이 모듈에서 생성한 프로젝트 API 핸들러 및 컨트롤러를 사용하여 자체 사용자 지정 코드를 작성하고 Snyk App을 원하는 대로 작동하도록 만드는 데 필요한 모든 것을 가졌어야 합니다.

우리는 v1 API를 사용했지만 Snyk의 REST API를 주시하십시오. 추가 기능이 추가될 때, Snyk 앱에서 사용할 새로운 또는 효율적인 엔드포인트를 찾을 수 있습니다.

Last updated