클래스룸 부가기능 만들기

이 워크스루는 클래스룸 부가기능 워크스루 시리즈의 첫 번째 워크스루입니다.

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

이 워크스루에서는 다음을 완료합니다.

  • 부가기능을 위한 새 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 예에서는 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를 입력하라는 메시지가 표시됩니다. 이 URI는 사용자가 부가기능을 열 때 로드될 것으로 예상되는 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을 입력합니다. 이전 단계에서 앱 공개 상태를 비공개로 설정한 경우 자리표시자일 수 있습니다.

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

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의 스토어 등록정보 페이지 상단에 있는 링크를 사용하여 부가기능을 설치할 수 있습니다. 페이지 상단의 앱 URL을 클릭하여 등록정보를 확인한 다음 설치를 선택합니다.

기본 웹 앱 빌드

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

iframe의 웹 앱 예

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

  • /: 시작 메시지와 현재 탭과 부가기능 iframe을 모두 닫는 버튼을 표시합니다.
  • /addon-discovery: 시작 메시지와 두 개의 버튼을 표시합니다. 하나는 부가기능 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이 열리고 부가기능이 마켓플레이스 SDK의 앱 구성 페이지에서 지정한 첨부파일 설정 URI를 로드합니다.

축하합니다. 이제 다음 단계인 Google SSO로 사용자 로그인을 진행할 수 있습니다.