클래스룸 부가기능 만들기

이 내용은 클래스룸 부가기능 둘러보기 시리즈의 첫 번째 둘러보기입니다.

이 둘러보기에서는 웹 애플리케이션을 개발하고 클래스룸 부가기능으로 게시하기 위한 기반을 마련합니다. 향후 둘러보기 단계에서는 이 앱을 확장합니다.

이 둘러보기 과정에서 다음을 완료합니다.

  • 부가기능을 위한 새 Google Cloud 프로젝트를 만듭니다.
  • 자리표시자 로그인 버튼이 있는 스켈레톤 웹 앱을 만듭니다.
  • 부가기능의 Google Workspace Marketplace 스토어 등록정보를 게시합니다.

완료되면 부가기능을 설치하고 클래스룸 부가기능 iframe에 로드할 수 있습니다.

기본 요건

언어를 선택하여 적절한 기본 요건을 확인하세요.

Python

Python 예시에서는 Flask 프레임워크를 사용합니다. 개요 페이지에서 모든 둘러보기의 전체 소스 코드를 다운로드할 수 있습니다. 이 둘러보기의 코드는 /flask/01-basic-app/ 디렉터리에서 찾을 수 있습니다.

필요한 경우 Python 3.7 이상을 설치하고 pip를 사용할 수 있는지 확인합니다.

python -m ensurepip --upgrade

또한 새 Python 가상 환경을 설정하고 활성화하는 것이 좋습니다.

python3 -m venv .classroom-addon-env
source .classroom-addon-env/bin/activate

다운로드한 예의 각 둘러보기 하위 디렉터리에는 requirements.txt가 포함되어 있습니다. pip를 사용하여 필요한 라이브러리를 빠르게 설치할 수 있습니다. 다음을 사용하여 이 둘러보기에 필요한 라이브러리를 설치합니다.

cd flask/01-basic-app
pip install -r requirements.txt

Node.js

Node.js 예시에서는 Express 프레임워크를 사용합니다. 개요 페이지에서 모든 둘러보기의 전체 소스 코드를 다운로드할 수 있습니다.

필요한 경우 NodeJS v16.13 이상을 설치합니다.

npm를 사용하여 필수 노드 모듈을 설치합니다.

npm install

Java

Java 예에서는 Spring Boot 프레임워크를 사용합니다. 개요 페이지에서 모든 둘러보기의 전체 소스 코드를 다운로드할 수 있습니다.

컴퓨터에 아직 Java 11+가 설치되어 있지 않으면 설치합니다.

Spring Boot 애플리케이션은 Gradle 또는 Maven을 사용하여 빌드를 처리하고 종속 항목을 관리할 수 있습니다. 이 예에는 Maven 자체를 설치할 필요 없이 성공적인 빌드를 보장하는 Maven 래퍼가 포함되어 있습니다.

제공된 예를 실행하려면 프로젝트를 다운로드한 디렉터리에서 다음 명령어를 실행하여 프로젝트를 실행하기 위한 기본 요건이 있는지 확인하세요.

java --version
./mvnw --version

또는 Windows에서는 다음 스크립트를 실행합니다.

java -version
mvnw.cmd --version

Google Cloud 프로젝트 설정

Classroom API에 대한 액세스 및 필수 인증 방법은 Google Cloud 프로젝트에서 제어합니다. 다음 안내에서는 부가기능에 사용할 새 프로젝트를 만들고 구성하는 최소 단계를 설명합니다.

프로젝트 만들기

프로젝트 만들기 페이지로 이동하여 새 Google Cloud 프로젝트를 만듭니다. 새 프로젝트에 원하는 이름을 지정할 수 있습니다. 만들기를 클릭합니다.

새 프로젝트가 완전히 생성되려면 몇 분 정도 걸립니다. 완료되면 프로젝트를 선택해야 합니다. 화면 상단의 프로젝트 선택기 드롭다운 메뉴에서 선택하거나 오른쪽 상단의 알림 메뉴에서 프로젝트 선택을 클릭합니다.

Google Cloud 콘솔에서
프로젝트 선택

Google Workspace Marketplace SDK를 Google Cloud 프로젝트에 연결합니다.

API 라이브러리 브라우저로 이동합니다. Google Workspace Marketplace SDK를 찾습니다. 결과 목록에 SDK가 표시됩니다.

Google Workspace Marketplace SDK 카드

Google Workspace Marketplace SDK 카드를 선택한 다음 사용 설정을 클릭합니다.

Google Workspace Marketplace SDK 구성하기

Google Workspace Marketplace는 사용자와 관리자가 부가기능을 설치하는 데 사용하는 목록을 제공합니다. 계속하려면 Marketplace SDK의 앱 구성스토어 등록정보, OAuth 동의 화면을 구성합니다.

앱 구성

Marketplace SDK의 앱 구성 페이지로 이동합니다. 다음 정보를 입력합니다.

  • 앱 공개 상태Public 또는 Private로 설정합니다.

    • 공개 설정은 최종적으로 최종 사용자에게 출시될 앱을 위한 것입니다. 공개 앱은 최종 사용자에게 게시하기 전에 승인 프로세스를 거쳐야 하지만, 앱을 설치하고 테스트할 수 있는 사용자를 초안으로 지정할 수 있습니다. 게시 전 상태로, 이를 통해 부가기능을 전송하기 전에 부가기능을 테스트하고 개발할 수 있습니다.
    • 비공개 설정은 내부 테스트 및 개발에 적합합니다. 비공개 앱은 프로젝트를 만든 도메인과 동일한 도메인에 있는 사용자만 설치할 수 있습니다. 따라서 프로젝트가 Google Workspace for Education을 구독하는 도메인에서 생성된 경우에만 공개 상태를 비공개로 설정해야 합니다. 그렇지 않으면 테스트 사용자가 클래스룸 부가기능을 실행할 수 없습니다.
  • 도메인 관리자로 설치를 제한하려면 설치 설정Admin Only install로 설정합니다.

  • 앱 통합에서 클래스룸 부가기능을 선택합니다. 보안 연결 설정 URI를 묻는 메시지가 표시됩니다. 이 URL은 사용자가 부가기능을 열 때 로드될 것으로 예상되는 URL입니다. 이 둘러보기에서는 https://<your domain>/addon-discovery이어야 합니다.

  • 허용된 연결 URI 프리픽스courses.*.addOnAttachments.createcourses.*.addOnAttachments.patch 메서드를 사용하여 AddOnAttachment에 설정된 URI의 유효성을 검사하는 데 사용됩니다. 이 유효성 검사는 리터럴 문자열 프리픽스 일치이며 현재 와일드 카드 사용을 허용하지 않습니다. 최소한 콘텐츠 서버의 루트 도메인(예: https://localhost:5000/ 또는 https://cdn.myedtech.com/)을 추가합니다.

  • 이전 단계의 OAuth 동의 화면에 제공된 것과 동일한 OAuth 범위를 추가합니다.

  • 개발자 링크에서 조직에 해당하는 필드를 작성합니다.

스토어 등록정보

Marketplace SDK의 스토어 등록정보 페이지로 이동합니다. 다음 정보를 입력합니다.

  • 앱 세부정보에서 언어를 추가하거나 이미 나열된 언어 옆의 드롭다운을 펼칩니다. 애플리케이션 이름과 설명을 제공합니다. 이 정보는 부가기능의 Google Workspace Marketplace 스토어 등록정보 페이지에 표시됩니다. 완료를 클릭하여 저장합니다.
  • 부가기능의 카테고리를 선택합니다.
  • 그래픽 애셋에서 필수 입력란에 이미지를 입력합니다. 나중에 변경할 수 있으며 지금은 자리표시자가 될 수 있습니다.
  • 지원 링크에 요청된 URL을 입력합니다. 이전 단계에서 앱 공개 상태를 비공개로 설정한 경우 이 URL은 자리표시자가 될 수 있습니다.

이전 단계에서 앱 공개 상태를 비공개로 설정한 경우 게시를 클릭합니다. 그러면 앱을 즉시 설치할 수 있습니다. 앱 공개 상태를 공개로 설정한 경우 테스트 사용자의 초안 테스터 영역에 이메일 주소를 추가하고 초안 저장을 클릭합니다.

OAuth 동의 화면은 사용자가 앱을 처음 승인할 때 표시됩니다. 사용 설정된 범위에 따라 앱에서 개인 및 계정 정보에 액세스하도록 허용하라는 메시지가 표시됩니다.

OAuth 동의 화면 만들기 페이지로 이동합니다. 다음 정보를 제공합니다.

  • 사용자 유형외부로 설정합니다. 만들기를 클릭합니다.
  • 다음 페이지에서 필수 앱 세부정보와 연락처 정보를 입력합니다. 승인된 도메인에서 앱을 호스팅하는 도메인을 제공합니다. 저장하고 계속하기를 클릭합니다.
  • 웹 앱에 필요한 OAuth 범위를 추가합니다. 범위 및 용도에 대한 자세한 내용은 OAuth 구성 가이드를 참조하세요.

    Google에서 login_hint 쿼리 매개변수를 전송할 수 있도록 다음 범위 중 하나 이상을 요청해야 합니다. 이 동작에 대한 자세한 내용은 OAuth 구성 가이드를 참조하세요.

    • https://www.googleapis.com/auth/userinfo.email (이미 포함됨)
    • https://www.googleapis.com/auth/userinfo.profile (이미 포함됨)

    다음은 클래스룸 부가기능에만 해당하는 범위입니다.

    • https://www.googleapis.com/auth/classroom.addons.teacher
    • https://www.googleapis.com/auth/classroom.addons.student

    또한 앱에 최종 사용자에게 필요한 다른 Google API 범위도 포함합니다.

    저장 후 계속을 클릭합니다.

  • 테스트 사용자 페이지에 테스트 계정의 이메일 주소를 나열합니다. 저장하고 계속하기를 클릭합니다.

설정이 올바른지 확인한 후 대시보드로 돌아갑니다.

부가기능 설치

이제 Marketplace SDK의 스토어 등록정보 페이지 상단에 있는 링크를 사용하여 부가기능을 설치할 수 있습니다. 페이지 상단의 App URL을 클릭하여 목록을 확인한 후 설치를 선택합니다.

기본 웹 앱 빌드

두 가지 경로로 기본 웹 애플리케이션을 설정합니다. 향후 둘러보기 단계에서는 이 애플리케이션을 확장하므로 지금은 부가기능 /addon-discovery의 방문 페이지와 '회사 사이트'의 모의 색인 페이지 /를 만듭니다.

iframe에 있는 웹 앱의 예

다음 두 엔드포인트를 구현합니다.

  • /: 환영 메시지와 현재 탭과 부가기능 iframe을 모두 닫는 버튼을 표시합니다.
  • /addon-discovery: 환영 메시지와 버튼 2개를 표시합니다. 하나는 부가기능 iframe을 닫는 버튼과 새 탭에서 웹사이트를 여는 버튼입니다.

창 또는 iframe을 만들고 닫을 수 있는 버튼이 추가됩니다. 이는 다음 둘러보기에서 승인을 위해 사용자를 새 탭으로 안전하게 표시하는 방법을 보여줍니다.

유틸리티 스크립트 만들기

static/scripts 디렉터리를 만듭니다. 새 파일 addon-utils.js을 만듭니다. 다음 두 함수를 추가합니다.

/**
 *   Opens a given destination route in a new window. This function uses
 *   window.open() so as to force window.opener to retain a reference to the
 *   iframe from which it was called.
 *   @param {string} destinationURL The endpoint to open, or "/" if none is
 *   provided.
 */
function openWebsiteInNewTab(destinationURL = '/') {
  window.open(destinationURL, '_blank');
}

/**
 *   Close the iframe by calling postMessage() in the host Classroom page. This
 *   function can be called directly when in a Classroom add-on iframe.
 *
 *   Alternatively, it can be used to close an add-on iframe in another window.
 *   For example, if an add-on iframe in Window 1 opens a link in a new Window 2
 *   using the openWebsiteInNewTab function, you can call
 *   window.opener.closeAddonIframe() from Window 2 to close the iframe in Window
 *   1.
 */
function closeAddonIframe() {
  window.parent.postMessage({
    type: 'Classroom',
    action: 'closeIframe',
  }, '*');
};

경로 만들기

/addon-discovery/ 엔드포인트를 구현합니다.

Python

애플리케이션 디렉터리 설정

이 예에서는 애플리케이션 로직을 Python 모듈로 구조화합니다. 이 디렉터리는 제공된 예에서 webapp 디렉터리입니다.

서버 모듈의 디렉터리를 만듭니다(예: webapp). static 디렉터리를 모듈 디렉터리로 이동합니다. 모듈 디렉터리에도 template 디렉터리를 만듭니다. HTML 파일은 여기에 만듭니다.

서버 모듈 빌드*

모듈 디렉터리에 __init__.py 파일을 만들고 다음 가져오기 및 선언을 추가합니다.

from flask import Flask
import config

app = Flask(__name__)
app.config.from_object(config.Config)

# Load other module script files. This import statement refers to the
# 'routes.py' file described below.
from webapp import routes

그런 다음 웹 앱의 경로를 처리할 파일을 만듭니다. 제공된 예에서는 webapp/routes.py입니다. 이 파일에 두 개의 경로를 구현합니다.

from webapp import app
import flask

@app.route("/")
def index():
    return flask.render_template("index.html",
                                message="You've reached the index page.")

@app.route("/classroom-addon")
def classroom_addon():
    return flask.render_template(
        "addon-discovery.html",
        message="You've reached the addon discovery page.")

두 경로 모두 message 변수를 각 Jinja 템플릿에 전달합니다. 사용자가 어느 페이지에 도달했는지 확인하는 데 유용합니다.

구성 및 실행 파일 만들기

애플리케이션의 루트 디렉터리에서 main.pyconfig.py 파일을 만듭니다. config.py에서 보안 비밀 키를 구성합니다.

import os

class Config(object):
    # Note: A secret key is included in the sample so that it works.
    # If you use this code in your application, replace this with a truly secret
    # key. See https://flask.palletsprojects.com/quickstart/#sessions.
    SECRET_KEY = os.environ.get(
        'SECRET_KEY') or "REPLACE ME - this value is here as a placeholder."

main.py 파일에서 모듈을 가져오고 Flask 서버를 시작합니다.

from webapp import app

if __name__ == "__main__":
    # Run the application over HTTPs with a locally stored certificate and key.
    # Defaults to https://localhost:5000.
    app.run(
        host="localhost",
        ssl_context=("localhost.pem", "localhost-key.pem"),
        debug=True)

Node.js

경로는 다음 줄과 함께 app.js 파일에 등록됩니다.

const websiteRouter = require('./routes/index');
const addonRouter = require('./routes/classroom-addon');

app.use('/', websiteRouter);
app.use('/addon-discovery', addonRouter);

/01-basic-app/routes/index.js를 열고 코드를 검토합니다. 이 경로는 최종 사용자가 회사 웹사이트를 방문할 때 연결됩니다. 경로는 index Handlebars 템플릿을 사용하여 응답을 렌더링하고 titlemessage 변수를 포함하는 데이터 객체를 템플릿에 전달합니다.

router.get('/', function (req, res, next) {
  res.render('index', {
    title: 'Education Technology',
    message: 'Welcome to our website!'
  });
});

두 번째 경로 /01-basic-app/routes/classroom-addon.js를 열고 코드를 검토합니다. 최종 사용자가 부가기능을 방문할 때 이 경로에 연결됩니다. 이 경로에서는 discovery Handlebars 템플릿과 추가로 addon.hbs 레이아웃을 사용하여 페이지를 회사 웹사이트와 다르게 렌더링합니다.

router.get('/', function (req, res, next) {
  res.render('discovery', {
    layout: 'addon.hbs',
    title: 'Education Technology Classroom add-on',
    message: `Welcome.`
  });
});

Java

자바 코드 예에서는 모듈을 사용하여 순차적 둘러보기 단계를 패키징합니다. 첫 번째 둘러보기이므로 코드는 step_01_basic_app 모듈 아래에 있습니다. 모듈을 사용하여 프로젝트를 구현하지 않아도 됩니다. 대신 둘러보기의 각 단계를 진행하면서 단일 프로젝트를 기반으로 빌드하는 것이 좋습니다.

이 예시 프로젝트에서는 컨트롤러 클래스 Controller.java를 만들어 엔드포인트를 정의합니다. 이 파일의 spring-boot-starter-web 종속 항목에서 @GetMapping 주석을 가져옵니다.

import org.springframework.web.bind.annotation.GetMapping;

클래스 정의 위에 Spring 프레임워크 컨트롤러 주석을 포함하여 클래스의 목적을 표시합니다.

@org.springframework.stereotype.Controller
public class Controller {

그런 다음 오류 처리를 위한 두 경로와 추가 경로를 구현합니다.

/** Returns the index page that will be displayed when the add-on opens in a
*   new tab.
*   @param model the Model interface to pass error information that's
*   displayed on the error page.
*   @return the index page template if successful, or the onError method to
*   handle and display the error message.
*/
@GetMapping(value = {"/"})
public String index(Model model) {
  try {
    return "index";
  } catch (Exception e) {
    return onError(e.getMessage(), model);
  }
}

/** Returns the add-on discovery page that will be displayed when the iframe
*   is first opened in Classroom.
*   @param model the Model interface to pass error information that's
*   displayed on the error page.
*   @return the addon-discovery page.
*/
@GetMapping(value = {"/addon-discovery"})
public String addon_discovery(Model model) {
  try {
    return "addon-discovery";
  } catch (Exception e) {
    return onError(e.getMessage(), model);
  }
}

/** Handles application errors.
*   @param errorMessage message to be displayed on the error page.
*   @param model the Model interface to pass error information to display on
*   the error page.
*   @return the error page.
*/
@GetMapping(value = {"/error"})
public String onError(String errorMessage, Model model) {
  model.addAttribute("error", errorMessage);
  return "error";
}

부가기능 테스트

서버를 실행합니다. 그런 다음 교사 테스트 사용자 중 하나로 Google 클래스룸에 로그인합니다. 수업 과제 탭으로 이동하여 새 과제를 만듭니다. 부가기능 선택 도구에서 부가기능을 선택합니다. iframe이 열리고 부가기능이 Marketplace SDK의 앱 구성 페이지에서 지정한 첨부파일 설정 URI를 로드합니다.

수고하셨습니다 이제 다음 단계인 Google SSO로 사용자 로그인으로 진행할 준비가 되었습니다.