有限会社バーチャルテクノロジー

仕様

概要

vte.cx(ブイテックス)はReactなどのJavaScriptフレームワークを利用してWebサービスを作成することができるバックエンドサービス(BaaS)です。
vte.cxのアプリケーションは基本的にREST APIを利⽤するSPA(Single Page Application)になります。APIを介すため、サーバとクライアントは基本的に疎結合となり、可搬性の高いアプリケーションを作成できます。
 このドキュメントは主にvte.cxの仕様について説明したものです。サービスの作成やデプロイ方法など実践的な内容については ファンダメンタルズQiitaの記事を参照してください。

主な機能

  • アカウント管理機能を提供します。
    • アプリのユーザー登録やログインを簡単に実装できユーザー管理を行える機能です。
    • メール認証によるユーザ管理の他、グループ管理が可能です。
    • 新規ユーザのサインアップ、パスワードリセットなどの画面を提供します。また、管理者によるユーザ登録機能があります。
  • ドキュメント型データベースに対してREST APIによるデータアクセスが可能です。
    • ドキュメント型データベースはあらゆるリソースをツリー構造で管理します。これは、Windowsなどのファイルシステムに似た管理方法であり、多くの開発者が慣れ親しんでいるインターフェースです。
    • リソースはデータ以外にも、HTMLやJavaScriptなどの静的コンテンツや写真などのblobコンテンが扱えるため、PCやモバイルのランディングページ等のWebページCMS機能として使用できます。
    • REST APIでは、エンドポイントURLで示すリソースに対して、4つのHTTPメソッド(GET,POST,PUT,DELETE)を実行する形になります。
    • リソースを、JSONやXML、MessagePackなどの様々な形式に変換して取りだすことができます。
  • リソースのデータはエントリスキーマというものでスキーマ管理します。
    • テンプレート設定ファイルに項目を記述することでエントリスキーマを定義します。
    • エントリスキーマは項目の追加が可能なソフトスキーマといわれるもので、デプロイ後のサービス運用中であっても自由に項目の追加ができます。
    • テンプレートは管理画面から編集することができますが、設定ファイルを直接編集することもできます。
    • テンプレートからTypeScriptの型定義ファイルを生成することができます。(管理画面のTS型定義ファイル出力機能)
  • アクセス権限(ACL:Access Control List)などを設定できます。
    • コンテンツやフォルダに対してACLを設定することで、ユーザやグループ単位で公開/非公開設定などのアクセスコントロールができます。
    • 項目ごとにバリデーションルールやACL、Index、暗号化などを設定できます。
  • サーバサイドJavaScriptを実行することができます。
    • サーバサイドで実行するJavaScriptを使ってビジネスロジックを記述することができます。
    • Reactを利⽤してSSR(サーバサイドレンダリング)を実装することができます。
    • ReactとSSRを利用して帳票PDFを出力することができます。
    • excelやcsvのデータアップロード/ダウンロード、メール送信などを実行できます。
    • Google BigQueryに対してJavaScriptから直接SQL操作が可能であり、データ分析アプリケーションを容易に作成することができます。
    • ビルド完了と同時に更新ファイルがサーバに自動的にアップロードされるため動作確認をすぐに行うことができます。

 

サービス

サービスとはユーザが作成するWebアプリケーション単位に割り当てられる領域(ドメイン)のことで、サービス一つにつきvte.cxのサブドメインが一つ割り当てられます。いわゆる、マルチテナントになっており、サービスは完全に独立して動作するため他のサービスと干渉することはありません。
 サービスは、管理画面より作成してください。サービス作成に成功すると以下のようにサービス名のサブドメインが割り当てられます。

https://{サービス名}.vte.cx 

サービス名は、対象のサービスにログインしてGET /d/?_serviceを実行することでも取得できます。

サービスには以下のような状態(ステータス)があります。
 作成されたサービスはひとまず「サービス非公開(開発中):staging」となります。
この状態のサービスは、JavaScriptのローカル開発環境で使うことを想定しており、http通信しかできません。また無料で使えます。
 管理画面から「サービス公開中:production」に変更すると、今度はhttps通信しかできなくなります。この状態になると課金されるようになります。

creating : サービス新規作成中
staging : サービス非公開(開発中)
production : サービス公開中
blocked : システム管理者による強制停止
resetting : リセット中
deleting : 削除中
deleted : 削除済み
failure : 登録失敗

リソース管理とメタ情報

リソースの取得と階層表現

vte.cxにはリソースを階層で管理するドキュメント型データベースがあります。
リソースは/dから始まるエンドポイントURLにマッピングされ、REST APIによりエンドポイントURLで指定されたリソースに対して読み書きできます。 例えば、GET /d/fooを実行するとfooというリソースを取得できます。リソースはHTMLやJavaScriptなどのテキスト情報の他、写真や動画などのBlobコンテンツなどを扱えます。
また、/d/fooをフォルダに見立てて、その配下にリソースを格納することもできます。例えば、/d/foo/barは、fooの配下にあるbarというリソースを意味します。GET /d/foo?fというように、fパラメータをつけることで、配下にあるリソースの一覧を取得することができます。エントリが存在しない場合、HTTPステータスコード204を返却します。

リソースの登録更新

例えば、PUT /d/foo?_contentにより、fooに対して、テキストデータやblobデータを格納することができます。PUTの際、ペイロード(リクエストのBody)にはデータをraw形式で入れてください。最大サイズは100MiBです。また、Content-Typeには、multipart/form-dataではなく、image/pngやtext/htmlなど実際に格納したコンテンツに合ったタイプを指定してください。
?_contentパラメータを付けない場合、メタ情報の登録更新になります。(メタ情報については「メタ情報とATOM表現」を参照してください。
また、 FormDataオブジェクトを使って画像やCSVファイルをアップロードすることができます。詳しくは、「ファイルアップロード」を参照してください。
PUTはCSRF対策のためXHR通信(XMLHttpRequest)からのみ受け付けるようにしています。つまり、リクエストヘッダに「X-Requested-With: XMLHttpRequest」が設定されていなければエラーを返却します。(ステータス417)

メタ情報とATOM表現

GET /d/foo?eとeパラメータを付与することで、fooのメタ情報を取得できます。エントリが存在しない場合、HTTPステータスコード204を返却します。

以下はメタ情報をJSONで取得したものです。(XMLの属性であるhrefやrelには___hrefのように___が先頭に付くので注意してください。)

http://vtecxblank.vte.cx/d/foo?e

[
            {
                "sample": "テスト内容",
                "author": [
                    {
                        "uri": "urn:vte.cx:created:209"
                    }
                ],
                "id": "/foo,1",
                "link": [
                    {
                        "___href": "/foo",
                        "___rel": "self"
                    }
                ],
                "published": "2018-06-26T12:29:37.947+09:00",
                "updated": "2018-06-26T12:29:37.947+09:00"
            }
        ]

メタ情報はATOM(RFC4287)によって表現されます。ATOMは少々古いものではありますが、拡張性が高く、メタ情報の流通ためのフォーマットとして広く使われていることから、vte.cxでも採用しています。

vte.cxにおけるATOMの各項目は以下のような意味をもちます。

  • entryはリソースの最小単位であり、1件のリソースが1エントリになります。
  • feedの中に複数のentryがリスト形式で入る形になります。(JSONではfeed、entryは省略されます)
  • ATOM項目のうち、author、id、published、updatedなどは自動で値がセットされますが、title、subtitle、summary等はユーザによって自由にセット可能です。
    • author : uri属性に「urn:vte.cx:updated:{uid}」が代入される。(uidは更新者のユーザ識別子)
    • id:キーとリビジョンを組み合わせた形でシステムで一意となる(後述)
    • published、updated : 作成(更新)日時を「yyyy-MM-dd'T'hh:mm:ss.SSS+99:99」(ISO8601拡張)形式で代入される
  • contentはコンテンツ(リソース本体)の格納場所として使われます。
  • linkは別名(alias)や外部コンテンツのキー(以下の説明を参照)として使われます。
  • contributorはアクセス権限(ACL)を管理するために使われます。
  • rightsは設定情報などで使われます。この項目は暗号化されます。

また、以下のように、エントリの基本構造としてidとキーがある点に注意してください。

  • エントリは一つの<id>タグを持ちます。これは、システム全体で一意となるように、キーとリビジョンを組み合わせた形をしています。(上記エントリのidは、/foo,1) また、リビジョンはエントリの更新回数です。
    • idが同じということはそのエントリのデータが完全に同一であることを意味します。
    • エントリを登録すると、<id>タグに値を自動的に登録します。
    • エントリ更新時、idが存在すると楽観的排他チェックを行います。つまり、更新前の元のリビジョンと比較して値が異なれば既に更新されているとみなされ楽観的排他エラーとなります。ただし、更新するエントリにidが含まれていない場合は楽観的排他チェックを行いません。
    • エントリ削除時、指定されたリビジョンと更新前のものを比較することで楽観的排他チェックを行います。リビジョン指定が省略された場合はチェックを行いません。
  • <link>タグのrel="self"属性のhref属性がエントリの参照を表すキーです。
    • キーは「/path/to」のようなパス形式となります。階層は10階層まで持つことができ、子は無限に持つことができます。
    • キーには基本的に取得した際に指定したURLと同じものになりますが、別名キー(alias)で取得するとidとは異なるキーの値となります。
    • キーに含まれる名前には英数字と$、_(アンダースコア)が使えます。

上記のATOM項目以外の項目をエントリスキーマに記述することでユーザ独自の項目を定義することができます。上記の例では、sampleがユーザ定義項目になります。
詳細は、「エントリスキーマとユーザ定義項目」で説明します。

別名(alias)

エントリにlink rel=alternateタグを付与することで別のURLにマッピングすることが可能です。これを別名(alias)と呼んでいます。LinuxのシンボリックリンクやWindowsのショートカットフォルダのような機能です。
例えば、ECサイトのカテゴリ表示において、家電/パソコン/アンドロイドや、家電/スマートフォン/アンドロイドといったように、「アンドロイド」を異なるカテゴリに関連付けしたい場合があります。
これを実現するのが別名(alias)で、「アンドロイド」は一つのエントリであるにもかかわらず複数のカテゴリのパスから参照できるようになります。
これは、上位階層が変わったときでも、aliasの書き換えだけで下位のエントリは変更しないで済むという利点があります。
また、子要素が存在するフォルダにaliasを設定するとaliasのパスからもその子要素にアクセスできるようになります。

以下のエントリは実体が /foo/android で別名(alias)が /bar/android になります。

[
            {
                "id": "/foo/android,1",
                "link": [
                    {
                        "___href": "/foo/android",
                        "___rel": "self"
                    },
                    {
                        "___href": "/bar/android",
                        "___rel": "alternate"
                    }
                ]
            }
        ]

以下のようにリクエストすることでaliasを追加できます。
aliasは1つのエントリにつき最大100個まで追加することができます。

PUT /d?_addalias

<feed>
  <entry>
    <link rel="self" href="{キー}" />
    <link rel="alternate" href="{alias}" />
  </entry>
</feed>

また、PUT /d?_removealias で指定したaliasを削除できます。

メタ情報の形式をリクエストで指定する

メタ情報は、JSONの他にもXMLやMessagePackなどの様々な形式に変換して取りだすことができます。例えば、GET /d/foo?e&xとxパラメータを付与することでXMLとして取得でき、GET /d/foo?e&mなどmパラメータでMessagePackとして取得できます。どのような形式に変換されても項目の構造は同じ形になります。
パラメータの指定がない場合、デフォルトはJSON形式です。
ブラウザに表示する際は、JSONやMessagePackではなくXMLをおすすめします。なお、JSONについてはセキュリティー上の理由でブラウザに直接表示できないようにしています。(JSONPの禁止)
また、POSTやPUTのペイロード(リクエストのBody)はraw形式のみ許可しています。送信する際はXHR通信(XMLHttpRequest)である必要があります。フォームデータ(HTML Form)送信はCSRF対策のため受け付けられないようにしています。

リクエストパラメータの種類とContent-Typeは以下の通りです。
リクエストパラメータの予約語は1文字の英字、もしくは先頭 _(アンダースコア)で始まる文字列になります。これ以外のもの、つまり2文字以上で_で始まらない文字列についてはユーザのアプリケーションが自由に使えるパラメータです。

  • x : XML文字列 (Content-Type : text/xml)
  • m : MessagePack形式バイナリをDeflate圧縮 (Content-Type : application/x-msgpack)
    • リクエストヘッダに「Content-Encoding: deflate」があればリクエストデータは圧縮されていることを示します。
    • リクエストヘッダに「Accept-Encoding: deflate」があればレスポンスデータを圧縮します。
  • 上記パラメータが無い場合はJSON文字列 (Content-Type : application/json)
    • JSON文字列返却はXHR通信からのみ受け付けるようにしているため、リクエストヘッダに「X-Requested-With: XMLHttpRequest」が設定されていなければエラーを返却します。(ステータス417)

以下は、/foo配下をxmlで取得した例です。
http://vtecxblank.vte.cx/d/foo?f&x

<?xml version="1.0" encoding="UTF-8" ?>
<feed>
    <entry>
        <sample>テスト内容</sample>
        <author>
            <uri>urn:vte.cx:created:209</uri>
        </author>
        <id>/foo/bar,1</id>
        <link href="/foo/bar" rel="self"/>
        <published>2018-06-26T12:29:37.947+09:00</published>
        <updated>2018-06-26T12:29:37.947+09:00</updated>
    </entry>
</feed>


エントリスキーマとユーザ定義項目

ATOM項目以外の項目をエントリスキーマに記述することでユーザ独自の項目を定義することができます。実際に業務アプリケーションを作成する場合にはこのユーザ定義項目をいかに定義していくかが鍵になります。
 エントリスキーマにユーザ項目を定義するには、管理画面の「エントリスキーマ管理」で行うか、プロジェクトの/setup/_settings/template.xmlファイルを直接編集し、項目名などを記述することでできます。(管理画面で編集を行った後は、必ずプロジェクトのtemplate.xmlファイルも更新するようにしてください)
 システム運用中でもファイルの更新は可能であり、項目を追加して更新すると直ぐにシステムに反映されます。ただし、追加項目は常に要素の最後尾に追記する必要があります。途中の階層であっても同列の最後であれば項目を追加できます。

エントリスキーマの記述ルール

エントリスキーマには以下の記述ルールが適用されます。ルールを適用できるものは、項目の有無や親子関係、繰り返しといった構造の定義、数値や文字列、日付などの型の指定、必須チェックや最大/最小チェック、正規表現によるバリデーションチェックなどがあり、データ登録更新時にルールに合致しなければエラーとなります。

  • 項目名(型){多重度}!=正規表現 の形式で項目を記述します。
  • 項目名は2文字以上128文字以下の英数字および一部の記号(_や$)が使えます。ただし、数字で始まるものやハイフンは使用不可です。
  • インデントが下がると上記要素の子要素であることを示します。
  • 項目名()の中には型を指定します。型には、string,int,date,long,float,double,boolean,descがあります。
  • ()を省略した場合や前述した型以外が指定された場合はstringになります。型名は先頭は大文字小文字のどちらでも構いません。(case sensitiveではない)
  • string型に格納できる文字の最大サイズは10MiBです。これを超えるラージオブジェクトについては項目ではなくコンテンツとして扱ってください。
  • date型は以下の形式のものを受け付けます。
    • yyyy-MM-dd
    • yyyy-MM-dd HH
    • yyyy-MM-dd HH:mm
    • yyyy-MM-dd HH:mm:ss
    • yyyy-MM-dd HH:mm:ss.SSS
    • 上記の"-"を"/"にしたもの
    • 上記の" "を"T"にしたもの
    • yyyyMMdd
    • yyyyMMddHH
    • yyyyMMddHHmm
    • yyyyMMddHHmmss
    • yyyyMMddHHmmssSSS
    • 上記の各フォーマットについて、末尾にタイムゾーン([ISO 8601] +99:99、+9999、+99)を加えたもの
  • desc型は降順ソートを行うための項目となります。{降順ソートしたい項目名}_desc(desc)と指定してください。
    • 例えば、prop1_desc(desc)をエントリスキーマに定義するとprop1の値で降順ソートされます。
  • 項目名{} はmapを意味します。括弧の中は要素数の最大値を示し、省略すると1になります。ただし、int型やstring型の場合は要素数の最大値ではなく、int型の場合は最大値、string型の場合は最大の長さになります。
    • 要素数には子要素の最大繰り返し数を指定します。
    • item(int){3}の場合、itemに設定できる値は3が最大値となります。
    • item2(string){3}の場合、item2に設定できる文字数が3文字となります。
  • 項目名の最後に!を付けると必須項目となります。
    • 注意:!は型や繰り返しよりも後に記述します。=の直前です。
  • =に続けて正規表現を指定することでバリデーションを定義できます。指定した正規表現にマッチしないとエラーとなって入力を受け付けません。
    • 正規表現構文については、Regex Patternを参照してください。
    • 正規表現が指定されている項目はデータ登録時にバリデーションチェックを実行します。 
  • $によりXMLの属性やテキストノードを指定することができます。
    • 項目名の先頭が$のものはXMLにシリアライズしたときに属性となります。ただし、属性は同列の他の項目より先に記述する必要があります。
    • $$textを付けることでテキストノードとみなされ、子要素ではなく自身の要素に値を代入します。
  • title、subtitle、updatedといったATOM項目については、エントリスキーマに定義しなくても使えます。逆に定義済みであるATOM項目をユーザ定義項目として定義しようとすると重複エラーとなります。 

以下はエントリスキーマのサンプルです。/_settings/templateエントリの<content>タグに以下を記述することでエントリスキーマを設定できます。

idx
email
verified_email(Boolean) // Boolean型 
name
given_name
family_name
error
 errors{2}       // インデントでerrorの子要素。Mapで多重度は2
  domain
  reason
  message
  locationType
  location
 code(int){1~100}       // 1~100の範囲     
 message
subInfo
 favorite
  $attribute       // $で始まる項目はXMLの属性となる(項目の先頭に記述する)
  food!=^.{3}$    // !で必須項目を示す。もし{}があればそれよりも後に記述する。 food{}!=xxx など
  music=^.{5}$    
 favorite2
  food
   food1
 favorite3
  food
 hobby{}
  $$text           // $$textはXMLのテキストノードになる

上記エントリスキーマを利用するサンプルアプリケーションでは以下のようなリクエストになります。リクエストでは、スキーマに定義されている項目のうち実際に必要なものだけが使用され、また、リクエストの第一階層の項目に値が代入されていないものは出現しない項目となります。
 例えば、このエンドポイントへのリクエストには、emailやfamily_name、subinfoなどの項目だけが存在する一方で、errorなどの項目はレスポンスだけに存在します。
 このように、アプリケーションを設計する際、どのエンドポイントでどの項目を使うかについてグルーピングの定義が必要です。vte.cxでは管理画面の「エンドポイント管理」を利用してこれらを定義することができます。

POST /d/registration

[
            {
                "email": "email1", 
                "family_name": "管理者Y", 
                "given_name": "X", 
                "name": "管理者", 
                "subInfo": {
                    "favorite": {
                        "food": "カレー", 
                        "music": "ポップス1"
                    }
                }, 
                "verified_email": false
            }
        ]
    

レスポンス

[
            {
                "error": {
                    "code": 400, 
                    "errors": [
                        {
                            "domain": "vte.cx", 
                            "location": "Authorization", 
                            "locationType": "header", 
                            "message": "invalid header", 
                            "reason": "invalidAuthentication"
                        }
                    ], 
                    "message": "Syntax Error"
                }
            }
        ]

REST APIによるリソースの操作

リクエストパラメータの種類

GET /d/foo?fというように、fパラメータをつけることで、配下にあるエントリの一覧を取得することができます。取得可能なエントリの最大件数は?l={件数}パラメータで指定できます。(デフォルトは100件)
?l=*ですべてのエントリを取得できます。また、?cで配下のエントリ件数を取得できます。

  • f:配下のエントリ一覧を取得
  • l:取得可能なエントリの最大件数(*ですべて)
  • c:配下のエントリ件数を取得
  • p:カーソル文字列

カーソル文字列とフェッチリミッター機能

検索対象のデータが大量にある場合、ページネーションのように、1回のGETで取得するより複数のページにわけて検索する方がユーザ体験がよくなることがあります。ページネーションでは、取得可能なエントリの最大件数を絞って複数回検索することになります。続きのデータが存在する場合、レスポンスヘッダのx-vtecx-nextpageに次のページのカーソル文字列が自動的に設定されますので、?pにそのカーソル文字列を指定することで次ページを検索することができます。
 カーソル文字列が設定されるケースは、lパラメータで指定した件数を超えた場合とフェッチリミッター機能の上限に達した場合の2通りあります。
フェッチリミッター機能とは、サーバ負荷軽減のため検索条件検索(含む件数取得)や検索条件付きページネションにおいて一定の件数(50,000件)を検索(fetch)後に一旦終了するようにしている制御機能です。
検索件数上限に達すると、戻り値に検索したところまでのカーソル文字列を設定し、ステータス206(Partial Content)を返却します。
クライアントにおいて「p=カーソル文字列」の形でクエリ文字列に付加してリクエストすると、続きのページを取得できます。
なお、?l=*で検索した場合はフェッチリミッター機能は無視され、すべての検索が完了するまで実行されます。これを使用すると、データ件数が多い場合にサーバに大きな負荷がかかるため、通常は件数を指定するようにしてください。

ページネーション

?lパラメータで指定した件数だけ取得した後、さらに次に続くデータを検索したい場合にページ数を指定して検索できると便利です。これをページネーション機能といいます。
ページネーション機能では、 GET {Key}?{検索条件}&n={ページ数}をリクエストすることで、指定されたページ数のデータを取得できます。また、l={件数}パラメータを指定することで、1ページあたりの件数を指定できます。
ただし、ページネーション機能を使うにはあらかじめカーソル一覧(pageindex)を作成しておく必要があります。以下のリクエストを実行することでカーソル一覧がセッションに保持されます。検索時、指定したページ数のカーソル一覧がセッションに存在しない場合は400エラーとなり、「Please make a pagination index in advance. 」のメッセージが返ります。また、検索した結果、最終ページ数に満たない場合は実際に張った最終ページ数が返ります。

  • GET {Key}?{検索条件}&_pagination={最終ページ数}&l={件数}
  • GET {Key}?{検索条件}&_pagination={開始ページ数},{最終ページ数}&l={件数}

大量のカーソル一覧(pageindex)を張るのは時間がかかるため、最初のリクエストは最終ページ数を50ぐらいにとどめておくのがおすすめです。(フェッチの最大実行回数である5万件を超える件数を検索した場合には206(Partial Content)が返ります)
 まず50ページ分をカーソル一覧に登録し、一旦画面に表示させたうえで、さらに続きを表示させる必要があれば、次の50〜100ページを張るようにします。ただし、開始ページ数を指定する場合、セッションに「開始ページ数-1ページ」のカーソルが登録されていなければエラーになりますので、開始ページ数には必ず前回実行した最終ページを指定するようにしてください。

条件検索

以下のように、URLパラメータに条件を指定することで絞り込み検索ができます。条件は「項目名=値」の形、もしくは、「項目名-記号-値」の形で指定します。項目名には、テンプレートの階層を"."でつないだ名称を指定します。
値はJavaScriptのencodeURIComponent()関数などで必ずエンコードしてください。値にもし&が含まれていると区切り文字と認識され、値が分割されて誤動作してしまいますのでご注意ください。具体的な条件検索の例についてはvtecxblankのチュートリアル(tutorial_2.html)に載せていますのでこちらもご覧ください。

文法

https://{サービス名}.vte.cx/{Key}?f&{name}{=|-eq-|-lt-|-le-|-gt-|-ge-|-ne-|-rg-|-fm-|-bm-|-ft-}{value}&{name}{=|-eq-|-lt-|-le-|-gt-|-ge-|-ne-|-rg-|-fm-|-bm-|-ft-}{value}&...&l={n}&p={カーソル文字列}&{ソート項目名}-desc|asc

例:好きな食べ物がeggで価格が5000以下を昇順で最大件数20件取得

https://{サービス名}.vte.cx/{Key}?f&subInfo.favorite.food-eq-egg&price-lt-5000&l=20&price-asc

記号の種類と意味は以下の通りです。

  • eq : = (等しい)
  • lt : < (未満)
  • le : <= (以下)
  • gt : > (より大きい)
  • ge : >= (以上)
  • ne : != (等しくない)
  • rg : regex (正規表現に合致する)
  • fm : 前方一致
  • bm : 後方一致
  • ft : 全文検索
  • asc : 昇順ソート
  • desc : 降順ソート

前方一致では、指定した文字の先頭文字が一致する条件となり、後方一致では、末尾文字が一致する条件になります。SQLで例えると、-fm-町は、LIKE '町%'に相当し、-bm-田は、LIKE '%田'に相当します。
あいまい検索を実行するには正規表現を使います。例えば、 -rg-.*田町.*は、`LIKE '%田町%'に相当します。

また、Keyに*を付けることで前方一致検索が可能です。例えば、"GET /men/to*"を実行すると、"/men/tokyo"、"/men/tops" などのエントリを検索できます。

OR検索

&|(と&)でOR条件を囲むことでOR検索ができます。括弧を省略する&|も使用できます。以下はOR条件の例です。

  • &|(title-rg-.*aaa.*&flg=1&)&|(subtitle-rg-.*bbb.*&flg=1&)
  • &|title-fm-aaa&|title-fm-bbb

注意点としては、カッコの入れ子は無し。また、カッコの中でOR条件を指定するのは無しという点です。以下は例です。

  • NG: &|title-fm-aaa&|(title-fm-bbb&|&(flg-eq-3&|flg-eq-4&)&)
  • OK: &|title-fm-aaa&|(title-fm-bbb&flg-eq-3&)&|(title-fm-bbb&flg-eq-4&)

また、&条件は全ての条件に適用されます。以下の例1、2は同じ条件です。

  • 1: &|title-fm-aaa&|title-fm-bbb&flg=1
  • 2: &|(title-fm-aaa&flg=1&)&|(title-fm-bbb&flg=1&)

全文検索で複数項目を対象にする

全文検索では複数項目を対象にすることができます。例えば、「nameまたはaddressが田中」かつ「nameまたはaddressが港区」という条件指定は以下のようにします。 ただし、複数項目を対象にする場合は、全文検索index指定(/_settings/templateのrightsタグ)において、|を使って複数の項目名を指定する必要があります。詳しくは、「全文検索Indexの設定」を参照してください。

name|address-ft-田中&name|address-ft-港区

項目が同じ場合、カンマを使って検索ワードのAND条件を1つにまとめる表現も指定可能です。以下は上記と同じ条件になります。

name|address-ft-田中,港区

上記条件で期待できる検索結果は、具体的には以下のパターンになります。

  • 1. nameに 田中、addressに 港区
  • 2. nameに 港区、addressに 田中
  • 3. nameに 田中港区
  • 4. addressに 田中港区

Indexの設定

条件検索はメモリ内で実行しますが、データ量が多いとパフォーマンス悪化を招きます。そのような場合、Indexを設定することで、パフォーマンスを向上させることができます。
ただし、Index検索を適用できるのは検索条件の最初の項目(一番左の項目)のみとなります。2番目以降の項目についてはIndex検索で絞り込み後にメモリ内で検索を実行します。例えば、?foo=123&bar=456という条件で検索した場合、fooにIndex設定があればIndex検索して絞り込みを行い、それからbarの検索をインメモリで実行します。
 また、Index検索の条件はgt、ge、lt、le、完全一致、前方一致である必要があり、暗号化項目は指定できません。(暗号化項目については以下を参照)2つ目以降の検索条件についてはあいまい検索や暗号化項目の検索が可能です。
 Indexは検索パフォーマンスを向上させる一方、書込パフォーマンスを悪化させるため、Index設定は必要最低限にすべきです。

 Indexの設定はテンプレートで行います。テンプレートとは、(/_settings/template)エントリのことで、Index設定はtemplateエントリのrightsタグに記述します。(管理画面のエントリスキーマ管理でも設定できます)
 以下のように、左辺の項目名に続けて:(コロン)の後にフォルダの正規表現を記述することでIndexを設定できます。また、右辺に登録するURIのうち正規表現にマッチするものをIndexとして登録します。URIにはエントリの実体(self)や別名(alias)などを指定します。
 以下は、subInfo.favorite.food項目を/masterまたは/dataから検索した際にIndex検索となる設定例です。

{項目}:{フォルダの正規表現}
subInfo.favorite.food:/master|/data  

全文検索Indexの設定

通常の検索以外に全文検索を行うことができます。全文検索を行うには、下記の全文検索Indexを設定したうえで、検索条件の記号を{項目名}-ft-{値}のように指定してください。

 全文検索Indexの設定はテンプレート(/_settings/template)のrightsタグに記述します。
 以下のように、左辺の項目名に続けて;(セミコロン)の後にフォルダの正規表現を記述することで全文検索Indexを設定できます。また、右辺に登録するURIのうち正規表現にマッチするものを全文検索Indexとして登録します。URIにはエントリの実体(self)や別名(alias)などを指定します。
 以下は、subInfo.favorite.food項目を/masterまたは/dataから検索した際に全文検索となる設定例です。

{項目};{フォルダの正規表現}
subInfo.favorite.food;/master|/data  

複数項目が対象の場合、左辺において、|を使って複数の項目を記述してください。

注意:検索条件と全文検索Indexの左辺は完全一致である必要があります。つまり、以下のリクエストは、subInfo.favorite.food|subInfo.favorite.music-ft-XXXのように指定してください。(項目の順序も一致させてください)

subInfo.favorite.food|subInfo.favorite.music;/master|/data  

ソートの指定

項目名-ascもしくは、?s={項目}パラメータを指定すると、その項目は昇順でソートされます。また、項目名-desc指定でその項目は降順となります。
ただし、ソート指定はページネーション検索とともに使う必要があります。これらは、基本的にメモリに展開するインメモリソートになるためパフォーマンスが悪くなる可能性があります。一方、ソート指定する項目にIndexの設定がなされているとレスポンスは高速になります。ソート指定でインデックスが有効な場合は以下のとおりです。

  • インデックス指定されたキーと項目であること
  • 昇順
  • 検索項目の先頭にソート指定
  • OR検索でないこと

また、index項目を検索する場合、その項目は昇順ソートされているため、ソート指定は不要です。
ソート指定をした項目が存在しない場合は検索できませんので注意してください。

?s=@keyを指定するとキー(link hrefの項目)でソートします。ただし、インメモリに展開するため指定しない場合に比べパフォーマンスが悪くなる場合があります。(デフォルトではkeyの昇順でソートされますがインメモリには展開されません)

OR検索の場合、デフォルトではOR検索条件ごとにソートされたものを合わせて返却します。OR検索内のソート項目は、OR検索内の第一条件がインデックス指定であればインデックス項目の昇順となります。インデックス項目でない場合、keyの昇順となります。

降順ソートを行いたい場合、検索項目とは別に降順用の項目(desc型)を追加すると高速に降順ソートができます。例えば、prop1_desc(desc)をエントリスキーマに定義するとprop1の値で降順ソート用のindexが作成されます。つまり、prop1_desc(desc)をtemplateのcontentに追加して、rightsに、prop1_desc:/endpointを追加することで、検索時にs=prop1_descを指定すると降順ソートになります。
desc型は降順ソートのindexを目的としたもので、実際の値を取得することはできません。GETしてもエントリの中にその項目は現れません。
インメモリソートの項目名-desc指定で降順ソートを行う場合、降順用の項目(desc型)の追加は必要ありません。

nometaオプションとデータのバックアップ

_nometaオプションを付けてGET実行すると、id、author、published、updatedがついていないエントリが返ります。これは、データのバックアップを目的にしたもので、取得したFeedをPUT /d/?_bulkserial実行によりデータをリストアすることができます。(?_bulkserialオプションについては、「トランザクション処理と大量更新」を参照してください)
l=*と組み合わせて、GET /d/{キー}?f&_nometa&l=*と指定することでキー配下の全件のエントリを取得できます。

サーバの現在時刻の取得

GET /{d|s}?_nowでサーバの現在時刻を取得できます。

POSTによるエントリの登録

POSTにより複数のエントリを登録できます。(1回のPUTで最大1000件まで)
エントリのlink rel=selfタグのhref属性にキーを指定してPOSTを実行すると指定したキーで登録されます。以下を実行すると/foo/barエントリが登録されます。ただし、親フォルダである/fooは存在していなければなりません。
登録に成功すると、HTTPステータス201が返ります。エラーの場合はFeed.titleタグにエラーメッセージが返ります。

POST /d

<entry>
  ・・・
  <link href="/foo/bar" rel="self"/>     <!-- link rel="href" にKeyを指定します -->
</entry>

link rel=selfタグを省略して実行した場合、キーには自動的にユニークな番号が採番されて登録されます。ただし、URLに親階層フォルダ(/foo)を指定する必要があります。

POST /d/foo

<entry>
  ・・・       <!-- link rel=self は設定しません -->
</entry>

PUTによるエントリの更新

PUTにより複数のエントリを同時に登録更新できます。(最大1000件まで)
このとき、登録されるエンドポイントはエントリのキー(link rel=selfタグのhref属性)の値になります。更新されると、HTTPステータスが200(登録時は201)で、メッセージfeed.title:"Updated."を返します。
 楽観的排他チェックを行う場合はidを指定してください。(GETで取得したidをそのまま指定してください。+1する必要はありません。)
楽観的排他チェックでは、指定したidのエントリがデータベースに存在するかどうかのチェックを行います。更新が成功すると自動的にidがインクリメントされます。
 idを省略して更新すると楽観的排他チェックは行われず強制上書きとなります。また、PUTは登録も可能であり、既存のデータが存在しない場合は更新ではなく新規登録となります。登録では基本的にPOSTを使いますが一度に1000件以上の登録ができないため、実はPOSTを使う意味はほとんどありません。
また、PUTは部分更新であり以下のような注意点があります。

  • 第一階層の項目に値が代入されているものを丸ごと置き換えます。
    • 第二階層以下の項目が省略されている場合は更新されないことに注意してください。上書きで消したい場合はその項目に空白を代入する必要があります。
    • linkタグに関しては以下のように処理します。
      • rel属性の単位で設定されているものを置き換えます。

トランザクション処理と大量更新

POSTやPUTによりFeed更新する際に1トランザクションで実行できるエントリは1000件までです。1000件を超える大量のエントリを更新するには、PUT /d/?_bulk等を使って実行する必要があります。ただし、このとき内部では1000件ずつ分割して実行していますので一貫性は担保されません。

PUTによる大量更新では、以下のように、並列/直列、同期/非同期の4種類の実行方法があります。直列実行では、先頭のエントリから1000件づつ実行し完了をまってから次の1000件を実行します。

  • PUT /d/?_bulk リクエストデータにFeed (並列同期処理)
  • PUT /d/?_bulk&_async リクエストデータにFeed (並列非同期処理)
  • PUT /d/?_bulkserial リクエストデータにFeed (直列同期処理)
  • PUT /d/?_bulkserial&_async リクエストデータにFeed (直列非同期処理)

DELETEによるリソースの削除

DELETE によりリソースを削除できます。
例えば、 DELETE /d/fooを実行すると、fooのリソース(コンテンツとメタデータ)を削除します。このとき、/foo配下にエントリが存在すると「Can't delete for the child entries exist.」エラーとなります。その場合、DELETE /d/foo?_rfを実行すると配下のエントリを含めて削除することができます。
DELETE /d/foo?r=1のように、rパラメータでリビジョンを指定すると楽観的排他チェックを行います。

トランザクション内でDELETEを実行する方法

PUTにより複数のエントリを1トランザクションで登録更新できますが、以下の方法で登録更新だけでなく削除も1トランザクション内で実行できます。

PUTするエントリのうち以下のものが削除される

  • エントリのidの後に?_deleteを指定(楽観的排他チェックが実行される)
    • 例) /folder1/item,3?_delete
  • idに?_deleteのみ指定する(楽観的排他チェックは実行されない)
    • 例) ?_delete

レスポンスの形式

リソースに対してGETを実行するとコンテンツが返ります。ただし、高速化のためETagによるレスポンスコントロールを行っています。つまり、前回参照したコンテンツに更新がなければ、304 Not Modifiedステータスを返します。

?eや?fでメタデータを検索した場合はfeedが返ります。
POSTやPUTなどの場合は基本的なレスポンスの形式は以下のような形になります。

<feed>
  <title>{メッセージ}</title>
</feed>

エラーの場合、HTTPステータスのみが返る場合があります。
詳しくは、「HTTPステータスコードとメッセージ」を参照してください。

採番と採番カウンタ

リソースやメタ情報とは別に採番や採番カウンタを持つことができます。これは、在庫数などの管理や連番が必要なケースで使うことを想定しています。

以下のようなメソッドが用意されています。

  • allocids(採番)
    • GET|PUT /d/?_allocids={採番数}を実行することで採番処理を行います。
      • 指定された採番数だけ番号を採番します。カウンタ値はエンドポイント(キー)ごとに管理します。
      • 採番数にマイナスの値を指定することはできません。1以上の値の指定が必要です。
      • DatastoreのallocateIdsを使用するため値はランダム値になります。
    • エラーの場合はFeed.titleにエラーメッセージが返ります。
  • setids(採番カウンタへの値のセット)
    • PUT /d/{キー}?_setids={設定値}パラメータを指定することで採番カウンタに設定値をセットします。
    • エラーの場合はFeed.titleにエラーメッセージが返ります。
  • addids(採番カウンタの加算)
    • PUT /d/{キー}?_addids={加算数}を実行することで採番カウンタの加算処理を行います。
      • パラメータで指定した加算数だけ採番の値をプラスしてその値を返します。
      • 戻り値はFeed形式で、titleに加算後の現在値を設定します。
    • 加算数にはマイナスの値を指定することが可能です。
    • GET /d/{キー}?_addidsを実行すると現在の採番カウンタの値を返します。
    • エラーの場合はFeed.titleにエラーメッセージが返ります。
  • rangeids(採番カウンタの範囲指定)
    • POST /d/{キー}?_rangeids で、bodyに{start}-{end}の形式を指定して実行すると採番範囲を設定できます。
      • 採番範囲を1000から2000、つまり、初期値が1000で、2000を超えたら1000に戻るような設定にするには、bodyに1000-2000 を入れます。
    • GET /d/{キー}?_rangeidsで現在設定されている加算枠(addids用)を返却します。
    • 戻り値はFeed.titleに「Addids range has been set.」が返ります。
    • エラーの場合はFeed.titleにエラーメッセージが返ります。

ユーザ、グループ管理とアクセスコントロール

メールアドレスとユーザアカウントの違い

vte.cxではユーザ登録の際にメール認証によって本人確認を行うため、メールアドレスが必要になります。また、登録で使用したメールアドレスがログインIDとなります。
メールアドレスの表記は valid e-mail addressに従います。
しかし、メールアドレスは1つで複数の受信が可能となるような記述方法があるため一意にすることはできません。
例えば、Gmailでは+以降@までを無視して送信しますが受信では無視しません。 foo+1@gmail.comfoo@gmail.comで送信し、foo+1@gmail.comで受信します。また、ピリオド(.)も無視するため、foo@gmail.comf.oo@gmail.com は、Gmail 上では同じものとみなされます。
vte.cxでは上記のGmailのルールに合わせる形を取っており、メールアドレスとは別のアカウント管理を行っています。それはセキュリティ上の理由からで、ピリオドなどを無視する仕様が、Gmail アドレスを登録する Web サービス側の仕様と組み合わさることによってフィッシングのリスクを増加させるという指摘がなされているからです。詳細

ユーザアカウントの仕様

入力されたメールアドレスを以下のルールで変換しシステムで一意なユーザアカウントとします。

  • メールアドレスを小文字に変換
  • アカウント利用可能文字である、英数字、ハイフン(-)、アンダースコア(_)、@、$、ドット(.) 以外を削除
  • メールアドレスの@より前の文字列について、ドット(.)を削除

ユーザアカウントはuid(ユーザ識別番号)と関連づけられます。uidはシステムで自動的に振られる一意の連番です。

ユーザエントリ

  • ユーザは各サービスの/_user/{uid}エントリで管理されます。これをユーザエントリといいます。
    • contributor.emailにオリジナルのメールアドレス
    • summaryにユーザの状態(ステータス)
    • titleにユーザアカウント
    • subtitleにニックネーム

GET /d/?_whoamiを実行すると現在ログインしているユーザエントリが返ります。(ログインしていない場合、HTTPステータスコード401を返却します。)

<feed>
<entry>
  <contributor>
    <email>foo.bar@vte.cx</email>
  </contributor>
  <id>/_user/216,2</id>
  <link href="/_user/216" rel="self" />
  <summary>Activated</summary>
  <title>foobar@vte.cx</title>
  <subtitle>nickname</subtitle>
  <published>2018-06-09T11:08:38.122+09:00</published>
  <updated>2018-06-09T13:38:15.360+09:00</updated>
</entry>
</feed>

ユーザステータスには以下の種類があります。

  • 登録なし: Nothing
  • 仮登録:Interim
  • 本登録:Activated
  • 無効:Revoked
  • 退会:Cancelled

ユーザ登録画面から仮登録を実行するとステータスがInterimになりユーザに確認メールが送信されます。メール内のリンクをクリックすることで本人認証がなされステータスが本登録(Activated)に変わります。Activatedになることではじめてサービスを使うことができるようになります。
また、ユーザ管理者によってユーザを追加することもできます。詳しくは、「管理者によるユーザ登録」を参照してください。
ユーザ管理者は PUT /d/?_revokeuser={ユーザアカウント}を実行することでアカウントを一時的に無効(Revokded)にすることができます。有効にするには、PUT /d/?_activateuser={ユーザアカウント}を実行します。
また、DELETE /d/?_canceluserで、ログインユーザのステータスを「Cancelled」(退会)に変更できます。これは、ログインユーザ自身のみ実行可能です。

uid検索

GET /d/?_uidを実行するとログイン中のuidを返却します。戻り値にはfeedのtitleにuidがセットされます。ログインしていない場合、HTTPステータス401を返却します。

ユーザアカウント検索

GET /d/?_accountを実行するとログイン中のユーザアカウントを返却します。戻り値にはfeedのtitleにユーザアカウントがセットされます。ログインしていない場合はHTTPステータス401を返却します。

ログインとログアウト

ログイン画面からログインIDとパスワードを入力して実行することでログイン認証が行われセッションを開始します。このとき、ログイン画面からはGET /d/?_loginが実行されます。また、パスワードはハッシュ化され、ワンタイムトークン(WSSE)としてリクエストされます。2回のログイン認証に失敗するとreCAPTCHAによる認証が必要になります。ログインロジックの詳細については、vtecxblankプロジェクトのログイン画面のソースを参照してください。ユーザアプリケーション作成の際にはこのログイン画面をカストマイズするとよいでしょう。
GET /d/?_logoutが実行されるとログアウトされセッションが破棄されます。

ユーザ仮登録

ユーザ仮登録はPOST /d/?_adduserで以下のエントリを登録すると実行されます。
機械的に実行されることを防ぐためreCAPTCHAが要求されます。

<feed>
  <entry>
    <contributor>
      <uri>urn:vte.cx:auth:{ユーザアカウント},{パスワード}</uri>
      <name>{ニックネーム}</name>
    </contributor>
    <title>{メールのタイトル(省略可)}</title>
    <summary>
    {テキストメール本文(省略可)}
    </summary>
    <content>
   {HTMLメール本文(省略可)}
    </content>
  </entry>   
</feed>

上記を実行すると、summaryタグもしくは、contentタグに記述された文章とタイトル(titleタグ)がメール送信されます。contentにはHTMLメール(任意)を、summaryにはテキストメール(必須)を記述してください。HTMLメールを表示できない場合はテキストメールになります。
メール送信内容は、以下のように /_settings/adduserエントリにも記述できます。上記エントリのsummary、content、titleが省略された場合は、/_settings/adduserエントリの内容でメール送信します。上記エントリのメール送信内容が存在せず、かつ、/_settings/adduserエントリーも存在しない場合、?_adduserがリクエストされるとHTTPステータス412(Precondition Failed)が返ります。
また、メール送信するには、 /_settings/propertiesにメール送信設定が記述されている必要があります。詳しくは、各種設定情報の「プロパティ情報」を参照してください。

<entry>
  <link rel="self" href="/_settings/adduser" />
  <title>{メールのタイトル}</title>
  <summary>
{テキストメール本文}
  </summary>
  <content>
{HTMLメール本文}
</content>
</entry>

メールの本文には、ワンタイムトークン(RXID)が付いたリンクを含む必要があります。メールの本文に${RXID=Key}を挿入することで、RXIDを含むURLが自動的に組み立てられます。ユーザがこのリンクをクリックするまでは仮登録となります。

初期フォルダ作成

 また、以下のようにadduserinfoエントリのsummaryタグにフォルダのキーを設定することで、最初にログインしたタイミングで自動的にフォルダが作成されます。
キー情報の"#"部分は登録時のユーザ(uid)で置換されます。

<entry>
  <link rel="self" href="/_settings/adduserinfo" />
  <summary>
{ユーザ登録時に作成するフォルダのキー}
/#/aaaa
/#/bbbb
....
  </summary>
</entry>

vtecxblankプロジェクトには、ユーザ仮登録の画面が用意されています。ユーザアプリケーション作成の際にはこの画面をカストマイズするとよいでしょう。

パスワードリセット

パスワードリセットはPOST /d/?_passresetで以下のエントリを登録することで実行されます。機械的に実行されることを防ぐためreCAPTCHAが要求されます。

<feed>
  <entry>
    <contributor>
      <uri>urn:vte.cx:auth:{ユーザアカウント}</uri>
    </contributor>
    <title>{メールのタイトル(省略可)}</title>
    <summary>
    {テキストメール本文(省略可)}
    </summary>
    <content>
   {HTMLメール本文(省略可)}
    </content>
  </entry>
</feed>

上記を実行すると、summaryタグ(テキストメール)もしくは、contentタグ(HTMLメール)に記述された文章がメール送信されます。メール送信内容は、以下のように/_settings/passresetエントリにも記述できます。上記エントリのsummary、content、titleが省略された場合は、/_settings/passresetエントリの内容でメール送信します。
メールにはワンタイムトークン(RXID)リンクを含む必要があります。
ユーザがこのリンクをクリックすることでパスワードを再設定します。
上記エントリにメール送信情報がなく、 /_settings/passresetエントリーにも存在しない場合、?_passresetがリクエストされるとHTTPステータス412(Precondition Failed)が返ります。

 vtecxblankプロジェクトには、パスワードリセットの画面が用意されています。ユーザアプリケーション作成の際にはこの画面をカストマイズするとよいでしょう。

パスワード変更

パスワード変更はPUT /d/?_changephashで以下のエントリを更新することで実行されます。ログイン中のアカウントに対して実行するため、reCAPTCHAは要求されません。

<feed>
  <entry>
    <contributor>
      <uri>urn:vte.cx:auth:,{パスワード}</uri>
    </contributor>
  </entry>
</feed>

vtecxblankプロジェクトには、パスワード変更の画面が用意されています。ユーザアプリケーション作成の際にはこの画面をカストマイズするとよいでしょう。

管理者によるユーザ登録

?_adduserByAdminパラメータを付けて以下を実行することで新規ユーザを登録します。

  • これはユーザ管理者のみが実行できます。
  • 複数のエントリを指定することで同時に複数のユーザを登録できます。
  • 本人認証(メールリンクをクリック)しなくても本登録になります。
  • ユーザ登録時に/_settings/adduserinfoに設定した初期フォルダが作成されます。
  • titleタグおよびsummary、contentにメール情報をセットすることで実行時にメールを送信することができます。
    • ただし、メール送信のための設定が/_settings/propertiesエントリに記述されている必要があります。詳しくは、各種設定情報の「プロパティ情報」を参照してください。
  • titleタグおよびsummary、contentが省略されており、かつ、/_settings/adduserByAdminエントリが登録されている場合、このエントリの内容でメールが送信されます。

POST|PUT /d?_adduserByAdmin

<feed>
   <entry>
     <contributor>
       <uri>urn:vte.cx:auth:{メールアドレス},{パスワード}</uri>
       <name>{ニックネーム}</name>
     </contributor>
     <title>メールのタイトル(任意)</title>
     <summary>テキストメール本文(任意)</summary>
     <content>HTMLメール本文(任意)</content>
   </entry>

   ...
 </feed>

管理者によるパスワード変更

?_changephashByAdminパラメータを付けて以下を実行することでパスワードを変更します。

  • これはユーザ管理者のみが実行できます。
  • ?_adduserByAdminで作成されたユーザのパスワードのみ変更できます。
  • リクエストデータに対象UIDとパスワードを指定します。指定したUIDのユーザのパスワードが更新されます。

PUT /d?_changephashByAdmin

<feed>
<entry>
<contributor>
<uri>urn:vte.cx:auth:,{パスワード}</uri>
</contributor>
<link href="/_user/{UID}/auth" rel="self" />
</entry>

...
</feed>

アカウント変更

PUT /d?_changeaccountでアカウント変更のためのメール送信を行います。これはログインユーザ自身のみ実行可能です。

  • メールのタイトルと本文についてリクエストに指定されていればそれを使用し、指定されていなければ/_settings/changeaccountエントリーの設定内容を送信します。
  • 実際にアカウントを更新する際には認証コードが必要になります。メール本文に認証コード変換文字列${VERIFY}を指定してください。
<feed>
<entry>
<contributor>
<uri>urn:vte.cx:auth:{メールアドレス}</uri>
</contributor>
<title>メールのタイトル(任意)</title>
<summary>メールの本文(任意)</summary>
</entry>
</feed>

PUT /d?_changeaccount_verify={認証コード}で実際にアカウント変更を実行します。

注) サービス作成者のアカウント変更を行う場合、作成したサービスとシステム管理サービスそれぞれについてアカウント変更を実施してください。 アカウント名が一致していないと、システム管理サービスから作成したサービスへのログインができません。

アカウント削除

DELETE /d?_deleteuser={アカウント|uid}で指定されたアカウントの削除を行います。アカウントかuidのいずれかを指定できます。

DELETE /d?_deleteuserで本文(body)にアカウントエントリを指定することで複数のユーザを削除できます。

<feed>
    <entry>
    <link rel="self" href="/_user/{uid}" />
    <title>{アカウント|uid}</title>
    </entry>
    ・・・
    </feed>

システムグループ

システムグループは複数のユーザーの権限をまとめて管理するために、システムがあらかじめ定義しているグループです。
つまり、 /_groupフォルダ配下にグループエントリ、その配下にuidエントリを作成し、/_user/{uid}/groupへのaliasを付与することで、ユーザ(uid)がシステムグループに参加したものとして認識されます。uidはサービスを作成した本人のものが使われます。

例:サービス管理者権限のグループエントリ

<feed>
  <entry>
    <id>/_group/$admin/216,3</id>
    <link href="/_group/$admin/216" rel="self" />
    <link href="/_user/216/group/$admin" rel="alternate" />
    <published>2018-06-07T14:32:40.444+09:00</published>
    <updated>2018-06-09T11:08:47.328+09:00</updated>
  </entry>
</feed>

/_group/$adminグループに属することでサービス管理権限が付与されます。
システムグループには以下のものがあります。
(システムグループは $adminのように$で始まる名前のグループです)

  • サービス管理権限:/_group/$admin
    • サービスの作成、削除、アプリケーションログの参照やACLやindexなどの設定が可能
    • <contributor>タグおよび、<rights>タグの内容を自由に編集可能
    • /_settingsフォルダにはサービス管理権限のACL(CRUD)が付与されている
  • ユーザ管理権限:/_group/$useradmin
    • ユーザの作成、削除、ユーザステータスの変更、パスワード変更が可能
  • コンテンツ管理権限:/_group/$content
    • HTMLやJavaScript等のページやコンテンツの登録、削除が可能
    • <content>タグのテキストノードの内容を自由に更新可能

ユーザ作成グループ

ユーザ作成グループはユーザが自由に定義できるグループで/_user/{uid}配下にあるものです。
/_user/{uid}/group/{グループ名} というエントリを作成することで、ユーザ作成グループを作ることができます。
※ユーザ作成グループは署名が必要です。詳しくは署名を参照してください。

グループ署名のパターンには、以下の2つがあります。グループ署名は作成者、参加者双方の署名を必須とします。

  • ①親がグループを作成→子がそのグループに参加申請→親が署名して参加承認
  • ②親がグループを作成→親が子に参加依頼→子が署名して参加
例)
    * グループ名: /_user/123/mygroup -> created uid=123
* グループ作成者uid: 123 * グループ参加者uid: 456 ①の場合 * 子がそのグループに参加申請 * 以下のエントリーを登録 * idキー: /_user/123/mygroup/456 * alias: /_user/456/group/mygroup * 以下のキーに署名 * alias: /_user/456/group/mygroup uid=456 * 親が署名して参加承認 * idキー/_user/123/mygroup/456のtitleに署名。uid=123 * 参加者のグループ参加チェック * /_user/456/group配下をfeed検索 * /_user/456/group/mygroupのidキーが/_user/123/mygroup/456なので、署名検証を行う。(/_groupで始まらない、かつ自uidとグループ名のUIDが異なるため) * /_user/123/mygroup/456の署名検証を行う。uid=123 * /_user/456/group/mygroupの署名検証を行う。uid=456 ②の場合 * 親が子に参加依頼 * 以下のエントリーを登録 * idキー: /_user/123/mygroup/456 * alias: /_user/456/group/mygroup * 以下のキーに署名 * idキー: /_user/123/mygroup/456 uid=123 * 子が署名して参加 * alias/_user/456/group/mygroupのtitleに署名。uid=456 * 参加者のグループ参加チェック * /_user/456/group配下をfeed検索 * /_user/456/group/mygroupのidキーが/_user/123/mygroup/456なので、署名検証を行う。(/_groupで始まらない、かつ自uidとグループ名のUIDが異なるため) * /_user/123/mygroup/456の署名検証を行う。uid=123 * /_user/456/group/mygroupの署名検証を行う。uid=456

フォルダACL

vte.cxでは、これまで述べたように、エンドポイントURLをフォルダに見立てて、その配下にリソースを格納できます。また、そのフォルダにACLを設定することで自身および配下のエントリについてアクセスコントロールを設定できます。
ACLは以下のようにエントリのcontributorタグに指定します。特定のユーザ(uid)、ログイン済ユーザ(+)、すべてのユーザ( *)、あるいはグループ(GroupKey)を設定できます。グループ指定ではワイルドカードの指定も可能です。(/group*など)

<contributor>
    <uri>urn:vte.cx:acl:{uid|*|+|GroupKey},{C|R|U|D|E|.|/}</uri>
</contributor>

ACLの種類 (複数指定可能だが「E」「.」「/」のみの指定は不可)

  • C : 登録処理を許可
  • R : 検索処理を許可
  • U : 更新処理を許可
  • D : 削除処理を許可
  • E : サーバサイドJavaScriptからのみデータ操作可でHTTP(S)からのデータ操作が不可となる。
  • . : Own このエントリのみ適用される。配下のエントリには適用されない。
  • / : Low このエントリより配下のものについて適用される。指定したエントリ自体には適用されない。
  • Own.Low/いずれも設定されていない場合は両方(./)が付いているとみなされる

権限のスコープ

  • 数字 : ログインしているユーザ(uid)が対象
    • 先頭と末尾にワイルドカード(*)を指定可能
  • * : ログインしていないユーザを含むすべてのユーザが対象
  • + : ログインしているすべてのユーザが対象
  • スラッシュ(/)で始まるもの : グループ(GroupKey)が対象

ACLの適用範囲

  • 自身の階層を含む上位階層のACLが適用される。
    • つまり、自身にはACL設定がなくても親フォルダ(さらに上も)に設定されていれば親のACLが有効になる。
  • 親フォルダにACLが設定されて、かつ、自身にもACLが設定されている場合、自身の設定が優先される。
    • 同様に、配下のエントリにACLの設定がある場合、上位階層で設定されたACLではなく配下のエントリのACLが有効となる。
  • 検索結果のうち参照権限がないエントリについては検索結果に含められない。
  • aliasのキーについてもACLが適用される。

ACLの追加と削除

以下をリクエストすることでACLを追加できます。

PUT /d/?_addacl

<feed>
  <entry>
    <link rel="self" href="{キー}" />
    <contributor>
      <uri>urn:vte.cx:acl:{ACL対象},{権限}</uri>
    </contributor>
  </entry>
</feed>

また、PUT /d/?_removeaclで指定したACLを削除できます。 

項目ACL

/_settings/templateエントリのタグに記述することで項目に対してACLを設定することができます。ACLにはユーザおよびグループの読込(R)または書込(W)権限を指定できます。
以下のように、項目名に続けて=の後に、{uid|group}+{RW}の形式でACLを指定します。 ,(カンマ)で複数件指定できます。

subInfo.favorite.food=3+W,/grp1+W,/*+R   

/_settings/templateエントリのrightsタグにはIndexも指定できますが、以下のようにindexとACLを同時に記述することができます。:コロンの右辺がIndex、=の右辺がACLになります。

subInfo.favorite3.food:/[0-9]+/(self|alias)=1+W   

暗号化項目

項目名に続けて#を付けると暗号化項目となります。Index項目と暗号化項目はどちらか一つを指定できます。 暗号化では、vte.cx内部で持っている秘密Key(usersecret)とidと組み合せてハッシュ化したものが実際の暗号化において使われます。 
 以下はcontributor.uriを暗号化する設定例です。

contributor.uri#              

 また、以下のように、暗号化と項目ACLは同時に設定できます。これは、rightsの暗号指定、かつ、自身と/_group/$adminグループのRW権限の付与を意味します。

rights#=@+RW,/_group/$admin+RW   

システムフォルダと各種設定

システムフォルダ

_から始まるフォルダをシステムフォルダといいます。システムフォルダはシステムが管理する特殊なフォルダであり以下の種類があります。

  • /_group:システムグループフォルダ
    • システムグループを管理します。詳細は「システムグループ」を参照してください。
  • /_user:ユーザフォルダ
    • 配下にユーザを管理します。詳細は「ユーザエントリ」を参照してください。
  • /_html:htmlコンテンツフォルダ
    • 配下のエントリーはHTML、CSSやJavaScript、画像などのコンテンツを格納できます。Webサーバのrewrite機能により、/_html/ にマッピングされます。
  • /_settings:各種設定情報
    • 各種設定情報を管理します。詳細は「各種設定情報」を参照してください。
  • /_log:ログ
    • 配下にシステムやアプリケーションによって出力されたログが降順(最新が先頭)で記録されます。
    • ログには、updated : 更新日時、title : タイトル、subtitle : サブタイトル、summary : 内容が書かれます。
  • /_security:認証の失敗回数を格納
    • 詳しくは「DoS攻撃対策のIP Blacklist」を参照してください。

各種設定情報

vte.cxの設定情報は以下のように/_settings配下で管理されます。これらは、直接編集もできますが、管理画面の「メール・詳細設定」でも設定できます。(推奨)

  • /_settings/properties:プロパティ情報
  • /_settings/template:エントリスキーマやIndex、項目ACLなど
  • /_settings/bigquery.json:BigQuery用サービスアカウント秘密鍵ファイル
  • /_settings/adduser:ユーザ登録時(?_adduser)に送られるメール本文
  • /_settings/passreset:パスワードリセット時(?_passreset)に送られるメール本文

プロパティ情報

プロパティ情報(/_settings/properties)のrightsタグにおいて以下のような情報を設定できます。ここで設定されていないものについてはシステム内部で持つ設定情報がデフォルトで使用されます。RXIDの詳細については「認証キーとトークン」を参照してください。

_entry.number.default : エントリーGET時のデフォルト最大数 [100]
_rxid.minute : RXID(WSSE)有効時間(分) [120]
_rxid.counter.{連番}.{回数} : 同じRXIDを使用しても指定回数まで許可されるURLパターンを指定。
_session.minute : セッション有効時間 [30]

エラーページ設定

以下はエラーページの設定です。これは、アクセス時にエラーが発生した場合にリダイレクトするルールを設定します。
また、リダイレクトする際に、Cookieの「ERROR_STATUS」にHTTPステータスコードが、また「ERROR_MESSAGE」にエラーメッセージがセットされます。このCookieは10秒間だけ有効です。

_errorpage.{適用順}.{エラーページselfid}={PathInfoの正規表現} : エラー画面表示URLパターン(正規表現) 

confirm.html表示中にエラーの場合はerror.htmlに遷移する。それ以外のエラーはlogin.htmlに遷移する設定例

_errorpage.1.error.html=^/_html/confirm.html$
_errorpage.2.login.html=^/_html/.*$

メール送信設定

以下はメール送信の設定です。これが設定されていないとユーザ登録においてメール送信ができないため本人確認ができません。ご自身でEmailを用意して設定してください。

_mail.from.personal : EMailの送信元名
_mail.from : Emailのfrom
_mail.transport.protocol : smtpsなどのメール送信プロトコル
_mail.password : メール送信アカウントのパスワード
_mail.smtp.host : メール送信ホスト
_mail.smtp.port : メール送信ポート
_mail.smtp.auth : true/false

BigQuery接続設定

以下はBigQueryに接続するための設定です。
これとは別に、/_settings/bigquery.jsonに、サービスアカウントの秘密鍵JSONを登録する必要があります。BigQueryを使用するためにはGoogle Cloud Platformプロジェクトを作成し、BigQueryを有効にする必要があります。

 _bigquery.projectid : プロジェクトID
 _bigquery.dataset : データセット名
 _bigquery.location : ロケーション(デフォルト値は asia-northeast1)

プロジェクトの作成とIDの確認

まず、Googleアカウントを取得します。
(https://accounts.google.com) のログイン画面からアカウントを作成を選択しアカウントを作成します。
1.作成したアカウントで Google Cloud Platform にログインします。(https://console.cloud.google.com)
2.プロジェクトを作成します。
 左上のGoogle Cloud Platformという題名の隣にプロジェクトの選択 ▼というリスト項目が表示されているのでクリックします。
 既に別のプロジェクトを作成し選択している場合、プロジェクト名が表示されます。
 表示された画面右上の新しいプロジェクトを選択し、新規プロジェクトを作成します。
3.プロジェクトIDの確認
 Google Cloud Platform のホーム画面に表示されるプロジェクト情報にプロジェクトIDが表示されます。

Billingの設定

BigQueryを使用するにはBillingの設定が必要です。
Google Cloud Platform の左メニューから「お支払い」を選択し、請求先アカウントを追加します。

データセットの作成

Google Cloud Platform の左メニューからBigQueryを選択すると、BigQueryの管理画面が表示されます。(https://bigquery.cloud.google.com)
BigQuery管理画面の左メニューより、プロジェクト名の横の下三角▼をクリックします。
Create new datasetを選択し、データセットを作成します。
データセットID、ロケーションを入力します。

BigQueryのサービスアカウント秘密鍵の作成

サービスアカウントとは、Googleの各サービスに対する権限を持つアカウントです。Googleのサービスに対しAPIリクエストを行う際に認証情報として使用します。
Google Cloud Platform の左メニューからIAMと管理-サービスアカウントを選択します。
画面上部のサービスアカウント表題の隣にある+サービスアカウントを作成をクリックします。
  1.サービスアカウント名: 任意の値を入力
  2.Project role: BigQuery-BigQueryユーザーおよびBigQueryデータオーナーを選択
  3.新しい秘密鍵の提供にチェックを入れます。キーのタイプをJSONとします。
  4.保存をクリックします。
サービスアカウントが作成されます。同時に、JSON形式の秘密鍵がダウンロードされます。

認証キーとトークン

アクセストークン

アクセストークンは時間制限なしの認証トークンであり、スマホアプリからの認証の他、サービスデプロイ時の認証トークンとしても使われます。

ログイン後、GET /d/?_accesstoken で取得できます。 feed.titleにアクセストークンが入ったものが返ります。

<feed>
  <title>{Accesstoken}</title>
</feed>

リクエスト時にAccesstokenをリクエストヘッダに付与することで認証されます。
認証が成功してもログイン状態にはなりません。

Authorization : Token {Accesstoken} 

リンクトークン

リンクトークンはアクセストークンと同様に時間制限なしの認証トークンですが、アクセスできるURLに制限が付けられています。
 これは、メールに含まれるURLリンクとして使うことを想定しています。送信するメールの本文に以下のように${LINK=...}を挿入することで、Linktokenを含むURLが自動的に組み立てられます。

挿入文

${LINK=/setpass.html}&value=abc 

実際のメール本文

https://test.vte.cx/setpass.html?_token=xxx&value=abc 

URLリンク(上記Linktoken)をクリックするとsetpass.htmlが表示されます。
ただし、ログイン状態にはなりません。
Keyを /foo と指定した場合、 許可される操作は以下のみです。

  • /fooのエントリ検索、フィード検索が可能
  • /foo 配下のエントリをPOST可能(自動採番)
  • /foo エントリーをPOST、PUT、DELETE可能

また、リンクトークンは、ログイン後、GET ?_linktoken={Key1[,Key2],・・} をリクエストすることでも取得できます。 KeyにはURLのPathinfoを指定します。カンマで区切ることで複数指定できます。Keyに含まれる#はuidに変換されます。

アクセスキー

アクセスキーは、アクセストークンやリンクトークンを発行するために使用されるシークレットキーであり、サーバ内(/_user/{uid}/accesskeyエントリ)に保持しています。
 アクセスキーを更新することで、これまで発行したアクセストークンやリンクトークンを無効にすることができます。つまり、アクセストークンが悪意のある第三者に漏洩してしまった場合、そのアクセストークンに認可されているあらゆる操作が永久に実行可能になるため、アクセスキーを更新してこれを防ぐ必要があります。

PUT /d/?_accesskeyでアクセスキーを更新します。更新するとこれまで使っていたアクセストークンやリンクトークンは使えなくなります。

ワンタイムトークン(RXID)

ワンタイムトークン(RXID)は鍵付きハッシュを利用した認証トークンです。ワンタイムであり一度認証が実行されると同じものはもう使えません。これは、AndroidやiPhoneなどのスマホのログイン認証で使うことを想定しています。また、異なるサービス間で通信が必要になった場合にも使われることがあります。
トークン生成にはユーザアカウント、パスワード、APIキー(後述)、サービス名を使用します。ハッシュ化された文字列なのでネットワーク上で生のパスワードが流れる心配はありません。

ログイン後、GET /d/?_getrxidを実行することでRXIDを取得できます。
RXIDは、 GET /d/?_RXID={RXID文字列}で認証されます。認証後はログイン状態になります。また、以下のようにリクエストヘッダにつけて認証することもできます。

Authorization: RXID {RXIDトークン} 

RXIDの有効時間や実行可能回数は、設定ファイル(/_settings/propertiesのrightsタグ)に指定します。

  • _rxid.minute : 有効時間(分)。[デフォルト15分]
  • _rxid.counter.連番.回数 : 同じRXIDを使用しても指定回数まで許可されるURLパターンを指定
_rxid.minute=60
_rxid.counter.1.10000=^/_html/foo.html.*$

APIキー

APIキーはワンタイムトークン(RXID)などに使用されるクライアントシークレットキーです。また、サーバサイドJavaScriptにおいてサービスを跨ぐAPI実行の際にもAPIキーが必要になります。サービスに対してAPIキーを1つ発行します。

APIキーを更新したい場合は以下を実行します。サービス管理者が実行できます。

PUT /d/?_apikey

APIキーを更新すると、これを保持している全てのクライアントから認証ができなくなります。第三者に漏洩してしまった場合などはAPIキーを更新することで悪意のある操作を防ぐことができます。ただし、更新した場合は新しいAPIキーをクライアントや他のサービスに配布する必要があります
 APIキーを更新してもアクセストークンやリンクトークンには影響はありません。これらを無効にしたい場合はアクセスキーを更新してください。

スマホアプリからのログイン方法

スマホアプリからログインするにはRXIDを使います。
RXIDを生成するライブラリであるvtecxauthパッケージをnpmで公開しています。まず、nodeをインストールし、npm install vtecxauthでvtecxauthパッケージをインストールしてください。 vtecxauthパッケージの vtecxauth.getRXID()メソッドを実行することでRXIDを取得することができます。
パラメータには、ユーザアカウント、パスワード、サービス名、APIキーを指定します。APIキーは管理画面で確認できます。

vtecxauth.getRXID(username: string, password: string, servicename: string, apikey: string): string;

以下はRXIDを使った認証の例です。初回はRXIDで認証してcookieを保存し、2回目以降はcookieを使って認証してください。(サーバではセッションが生成されています)
セッションが不要な場合はcookieの代わりにアクセストークンを使用してください。アクセストークンを使う場合はセッションは生成されずタイムアウトもありません。

// 初回(RXIDでログイン)
const rxid = vtecxauth.getRXID(username, password, servicename, apikey)

axios({
    url: 'http://{サービス名}.vte.cx/d',
    method: 'get',
    headers: {
        'Authorization: RXID '+ rxid,
        'X-Requested-With': 'XMLHttpRequest'
    }
}).then((result) => {
    cookie = result.headers['set-cookie'][0].split(';')[0]  // cookieを保存
}).catch((error) => {
    ・・・
})

// 2回目以降(cookie認証)
axios({
    url: 'http://{サービス名}.vte.cx/d',
    method: 'post',
    headers: {
        'X-Requested-With': 'XMLHttpRequest',
        'Cookie': cookie
    },
    data : reqdata          
}).then((result) => {
    ・・・
}).catch((error) => {
    ・・・
})

署名

Entryに署名をつけることでユーザが承認したことを示す記録を残すことができます。グループにおけるメンバー承認のプロセスにおいても署名が使われます。

キーはselfまたはエイリアスに署名を付けることができます。ただし、キーのuidはログインユーザと一致する必要があります。つまり、/_user/{uid}で始まるキーに対してのみ署名可能です。そうでない場合、署名不可エラーとなります。

    <link rel="{self | alternate}" href="{キー}" title="{revision},{uid},{署名}" />

以下で署名を付与できます。

PUT {キー}?_signature&r={リビジョン(任意)}

正常に署名できた場合、HTTPステータスコード200(A signature has been applied.)が返ります。ログインしていない場合、401が、指定されたKeyに署名する権限がない場合、403が返ります。

既に署名が付いている場合は更新します。更新の際はRevisionを+1し、更新時刻を新たに設定します。署名ができるのは、idのキー、またはエイリアスのキーのみです。

以下で署名を削除できます。

DELETE {キー}?_signature&r={リビジョン(任意)}

削除が成功すると、linkタグのtitle属性に"-"(署名削除)を設定します。(署名がないものとの区別が必要になるケースがあるため、ブランクにするのでなく、無効な値(-)をセットする必要があります。)

正常に署名を削除できた場合、HTTPステータスコード200が返ります。ログインしていない場合、401が、指定されたKeyに署名する権限がない場合、403が返ります。

署名削除の際も通常の更新と同じくRevisionを+1し、更新時刻を新たに設定します。

以下で署名を検証できます。

GET {キー}?_signature

指定するキーは、署名検証したいキーです。キーでなくエントリーそのものの参照権限がある場合、署名検証を可能とします。

正常に署名を検証できた場合、HTTPステータスコード200が返ります。署名が不正である場合、412(The signature is invalid.)が返ります。キーのエントリーそのものを参照する権限がない場合、403が返ります。

DoS攻撃対策

IP Blacklist

ブラックリストに登録することでDoS攻撃などの可能性があるリクエストを拒否(ブロック)できます。ブラックリストに登録されているユーザ・IPアドレスの組み合わせからのリクエストは認証エラーとなります。特定のIPから1000回(デフォルト)以上ログイン失敗でユーザ・IPアドレスの組み合わがブラックリストに自動的に追加されます。ブラックリストに追加されても異なるIPからログインすることは可能です。

ブラックリストは認証失敗カウンタによって管理します。認証失敗カウンタは、/_security/{アカウント認識文字列}/{IPアドレス}キーのカウンタが使われます。

  • アカウント認識文字列は、WSSEやRXIDの場合はアカウント (ユーザ名から使用不可文字を除去した文字列)が、AccessTokenやLinkTokenの場合はuidが使われる

ブラックリストのブロックは最後に認証失敗してから24時間後(デフォルト)に解除されます。または、認証失敗カウンタを0にすることでブロックを解除できます。具体的には以下のようなリクエストです。

ブロック解除

DELETE /d/_security/{アカウント認識文字列}/{IPアドレス}?_cachelong 

サーバサイドJavaScript

vte.cxのアプリケーションを作成すると基本的にSPA(Single Page Application)になります。SPAのスタイルでは、サーバサイドでレンダリングするのではなく、クライアントからはAjax(XHR)を用いてサーバのAPIにアクセスし、サーバはJSONデータを返すのみとなります。サーバサイドは純粋なAPIとなり、複雑さがサーバサイドからクライアントに移動していることから、これはThin server architecture とも呼ばれます。
 しかし、ビジネスロジックをもたない純粋なREST APIだけで構築すると開発生産性やパフォーマンスを大きく損ねることがあります。あまりにもクライアント側にビジネスロジックが偏重しすぎて開発工数が膨らんでしまうのです。また、クライアントから何回もAPIを呼ぶいわゆるN+1問題も発生しがちです。
 実はクライアントよりもサーバ側でビジネスロジックを実行する方が有利であることがわかっています。つまり、クライアントはビジネスロジックを持たない単純なViewとし、Viewに必要なデータの組み立てなどはサーバ側で行うことで、クライアント側へのビジネスロジック偏重を防ぎます。こうすることで、N+1問題も解決できます。
 このスタイルを、BFF(Backend for Frontend)といいます。vte.cxではサーバサイドのBFFにもJavaScriptを採用することで、統一的(Isomorphic)な環境で高い生産性を発揮できるようにしています。
 BFFは実はフロントエンドアプリケーションの範疇になります。重要なポイントは、あくまでフロントエンドのアプリケーションとしてサーバサイドJavaScriptを実装するということであり、データベース設定や認証設定などのようなサーバ構築のための設定などではないという点です。

サーバサイドJavaScriptの設定方法と実行の仕組み

サーバサイドJavaScriptを設定する方法は簡単で、/serverフォルダ配下にJavaScriptファイルを格納するだけです。また、GET|POST|PUT /s/{スクリプト名}のようにリクエストすると、/serverフォルダに格納されている {スクリプト名}.jsファイルがサーバ上で実行されます。
 サーバサイドJavaScriptの実行時間は同期リクエストで最大5分までで、それを超えると強制的にキャンセルされます。長時間かかる場合は_asyncパラメータを付けて非同期リクエストを実行してください。非同期リクエストでは別スレッドが起動し、ステータス202 Acceptedをレスポンスします。実際の処理はバッチジョブサーバで実行されるため、バッチジョブサーバに設定されたタイムアウト時間が最大の実行時間になります。

サーバサイドJavaScriptのビルドとデプロイ

サーバサイドJavaScriptは、TypeScriptで書かれたソースコードをWebpackなどによりECMAScript5の形式に変換されたものを実行します。 これは、以下のようにビルドします。

ログイン

 管理画面にてサービスを作成後(仮にfooserviceとします)、githubのブランクプロジェクトをチェックアウトして、npm installします。その後、以下のコマンドでログインします。アカウントとパスワードはサービス作成で使ったものと同じもの使用してください。

npm run login

service:fooservice
is production?:n
login:foo@bar.com
password:*********
Logged in.

ビルドとデプロイ

 以下のコマンドを実行するとssr.html.tsxをビルド&デプロイできます。また、ソースを更新すると自動的にビルド&デプロイされます。デプロイ後、/s/ssr.htmlをブラウザで開いて確認してください。

npm run watch — --env entry=/server/ssr.html.tsx

クライアントコードの開発

 以下のコマンドを実行するとクライアントコードの開発環境が開きます。(webpack-dev-serverが起動します)

npm run serve:index

 ログインが必要な場合は以下でログインページを開き、ログイン後に再び上記コマンドを実行してください。

npm run serve:login

 index.tsxとは異なるファイル名(以下の例はhello.tsx)を指定する場合は以下のようにしてください。

npm run serve — --env entry=/components/hello.tsx

Reactとvtecxapiを利用してSSRを実行する

vtecxapiパッケージをインポートすることでサーバサイドJavaScriptからvte.cxのAPIを利用することができます。まず初めにnpm install vtecxapiでvtecxapiパッケージをインストールしてください。
 以下はReactの機能を使ってSSR(サーバサイドレンダリング)を実行するサンプルです。最後の行のHTMLを返却するところでvtecxapiが使われています。
 以下をデプロイして、/s/ssr.htmlにアクセスすると、「Hello, World!」が表示されるのを確認できます。 

// server/ssr.html.tsx
import * as vtecxapi from 'vtecxapi'
import * as React from 'react'
import * as ReactDOMServer from 'react-dom/server'

const element = (
    <h3> Hello, World! </h3>
)

const html = ReactDOMServer.renderToStaticMarkup(element)

vtecxapi.doResponseHtml(html)

vtecxapiを利用してJSONを返すサービス

vtecxapi.doResponse()により、オブジェクトをJSONなどに変換してクライアントに返すことができます。サーバサイドのビジネスロジックはこのような形でJSONを返すサービスとして実装することが多いと思われます。
以下は/fooエントリを取得してJSONを返すサンプルです。
 前述のサンプルでは、コンテンツを返すのに、doResponseHtml()を使っていましたが、JSONなどのデータを返すには、doResponse()を使います。 ちなみに、doResponse()は、リクエストパラメータにデータタイプを指定することで様々なデータ形式に変換することができます。例えば、/registration?xでxml、/registration?mでMessagePackを返すことができます。

// server/registration.tsx
import * as vtecxapi from 'vtecxapi'

const feed = vtecxapi.getFeed('/registration')   // /registration?xでxmlになる
vtecxapi.doResponse(feed) 

また、vtecxapi.getFeed()は検索条件を指定して検索することができます。
検索条件の文字列に&が含まれる可能性がある場合は、encodeURIComponent()などを使って必ずエンコードしてください。レスポンスの最初のエントリのrightsに文字列(nextpagelink)が格納されている場合、次ページが存在することを示します。次のリクエストにて、p={nextpagelink}パラメータを付けることで次ページを取得できます。

// server/registration.tsx
import * as vtecxapi from 'vtecxapi'

const param = encodeURIComponent(vtecxapi.getQueryString('param'))
const feed = vtecxapi.getFeed('/registration?param='+param)   // /registration?xでxmlになる
vtecxapi.doResponse(feed) 

ファイルアップロード

リソースの登録更新では、raw形式のバイナリの他にFormDataオブジェクトを使ったアップロードを行えます。FormDataオブジェクトはmultipart/form-data形式のデータであり、ブラウザから直接アップロードすることができます。
アップロードファイルが一つでありキーも指定されていない場合、アップロードしたファイル名がエントリのKeyとして登録されます。アップロードファイルが複数の場合は以下のようにサーバサイドJavaScriptを使って処理することができます。
vtecxblankに複数のファイルを登録するサンプルプログラム(upload_pictures_sample)があります。これは、クライアントから2つの画像データを送信してサーバに登録するサンプルです。
サーバサイドJavaScriptのvtecxapi.saveFiles(param)は、ファイルアップロードにおけるMultipart Postリクエストをサーバ側で処理するために使用します。具体的には、saveFiles(param)で指定するparamオブジェクトに、inputタグのnameをキーにして任意のファイル名を設定します。
サービスを作成し、デプロイしてログインすると、/upload_pictures_sample.htmlを表示できますので、そこから2つの画像ファイルをアップロードしてみてください。アップロードに成功すると、http://{サービス名}.vte.cx/{アップロードファイル名}でブラウザに表示して確認できます。(※ サービスの作成方法については、チュートリアルを参照してください。)

画面側(upload_pictures_sample.tsx)

import * as React from 'react'
import * as ReactDOM from 'react-dom'
import axios, { AxiosError } from 'axios'
import {
    Form,
    FormGroup,
    FormControl,
    Button
} from 'react-bootstrap'

/* コンポーネントのPropsの型宣言 */
interface ComponentProps {
}
/* コンポーネントのStateの型宣言 */
interface ComponentState {
    picture1: any,
    picture2: any,
    [propName: string]: any
}

class UploadPictureForm extends React.Component<ComponentProps, ComponentState> {
    constructor(props: ComponentProps) {
        super(props)
        this.state = { picture1: {}, picture2: {} }
    }

    handleChange(e: React.FormEvent<any>) {

        if (e.currentTarget.files) {
            const file = e.currentTarget.files.item(0)
            if (file) {
                const key = '/_html/img/' + encodeURIComponent(file.name)
                const name = e.currentTarget.name

                // 画像以外は処理を停止
                if (!file.type.match('image.*')) {
                    return
                } else {
                    // 画像表示
                    let reader = new FileReader()
                    reader.onload = () => {
                        this.setState({ [name]: { value: reader.result, key: key } })
                    }
                    reader.readAsDataURL(file)
                }
            }
        }

    }

    handleSubmit(e: React.FormEvent<any>) {
        e.preventDefault()

        const formData = new FormData(e.currentTarget)
        const param = (this.state.picture1.key ? 'key1=' + this.state.picture1.key + '&' : '') +
            (this.state.picture2.key ? 'key2=' + this.state.picture2.key : '')

        // 画像は、/d/_html/img/{key} としてサーバに保存されます
        axios({
            url: '/s/savefiles?' + param,
            method: 'post',
            headers: {
                'X-Requested-With': 'XMLHttpRequest'
            },
            data: formData

        }).then(() => {
            alert('success')
        }).catch((error: AxiosError) => {
            if (error.response) {
                alert('error=' + JSON.stringify(error.response))
            } else {
                alert('error')
            }
        })

    }

    render() {
        return (
            <Form horizontal onSubmit={(e) => this.handleSubmit(e)}>
                <img src={this.state.picture1.value} />
                <br />
                <img src={this.state.picture2.value} />
                <br />
                <FormGroup>
                    <FormControl type="file" name="picture1" onChange={(e) => this.handleChange(e)} />
                </FormGroup>
                <FormGroup>
                    <FormControl type="file" name="picture2" onChange={(e) => this.handleChange(e)} />
                </FormGroup>
                <FormGroup>
                    <Button type="submit" className="btn btn-primary">
                        登録
                    </Button>
                </FormGroup>
            </Form>
        )
    }
}

ReactDOM.render(<UploadPictureForm />, document.getElementById('container'))

サーバサイドJavaScript(/server/savefiles.tsx)

import * as vtecxapi from 'vtecxapi'

interface Param {
    picture1: string
    picture2: string
}

const param: Param = {
    picture1: vtecxapi.getQueryString('key1'),
    picture2: vtecxapi.getQueryString('key2')
}

vtecxapi.saveFiles(param)

CSVデータアップロード

サーバサイドJavaScriptを利用することでCSVデータのアップロードを行うことができます。vtecxapi.getCsv(header[],items[],parent,skip,encoding)は、指定したパラメータを元にCSVを受信してJSONオブジェクトに変換します。パラメータの意味は以下の通りです。

  • headerにはCSVのヘッダ情報を指定する。 受信したCSVファイルのヘッダ情報と異なれば {"feed":{"entry":[{"title" : "Header parse error"}]}}のようにパースエラーとなる。
  • itemsには対応するJSONの項目名を指定する
    • item1(int)やitem1(boolean)のようにカッコの中にintやbooleanの型を指定可能
  • parentには変換後のJSONの親項目を指定する。
  • skipにはCSVファイルの読み飛ばす行数を指定する。
  • encodingはCSVファイルの文字コードを指定する。(UTF-8,Windows-31J等)

vtecxblankプロジェクトには以下のサンプルプログラムがあります。
デプロイしてログインすると、/upload_csv_sample.htmlを表示できますので、その画面から/data/sample.csvを指定してアップロードしてください。アップロードに成功するとログに読み取った結果のJSONを表示しますので、管理画面のログで確認してください。

画面側(upload_csv_sample.tsx)

import * as React from 'react'
import * as ReactDOM from 'react-dom'
import axios, { AxiosError } from 'axios'
import {
    Form,
    FormGroup,
    FormControl,
    Button
} from 'react-bootstrap'

/* コンポーネントのPropsの型宣言 */
interface ComponentProps {
}
/* コンポーネントのStateの型宣言 */
interface ComponentState {
}

class UploadCsvForm extends React.Component<ComponentProps, ComponentState> {
    constructor(props: ComponentProps) {
        super(props)
        this.state = {}
    }

    handleSubmit(e: React.FormEvent<any>) {
        e.preventDefault()

        const formData = new FormData(e.currentTarget)

        // 画像は、/d/registration/{key} としてサーバに保存されます
        axios({
            url: '/s/getcsv',
            method: 'post',
            headers: {
                'X-Requested-With': 'XMLHttpRequest'
            },
            data: formData

        }).then(() => {
            alert('success')
        }).catch((error: AxiosError) => {
            if (error.response) {
                alert('error=' + JSON.stringify(error.response))
            } else {
                alert('error')
            }
        })

    }

    render() {
        return (
            <Form horizontal onSubmit={(e) => this.handleSubmit(e)}>
                <FormGroup>
                    <FormControl type="file" name="csv" />
                </FormGroup>
                <FormGroup>
                    <Button type="submit" className="btn btn-primary">
                        登録
                    </Button>
                </FormGroup>
            </Form>
        )
    }
}

ReactDOM.render(<UploadCsvForm />, document.getElementById('container'))

サーバサイドJavaScript(/server/getcsv.tsx)

import * as vtecxapi from 'vtecxapi'

const items = ['item1', 'item2(int)', 'item3(int)']
const header = ['年月日', '件数', '合計']
const parent = 'order'
const skip = 1 // 1行スキップ
//const encoding = 'Windows-31J'
const encoding = 'UTF-8'

// CSV取得
const result = vtecxapi.getCsv(header, items, parent, skip, encoding)
vtecxapi.log(JSON.stringify(result)) 

CSVデータ(/data/sample.csv)

// 1行skip
年月日,件数,合計
"2017/7/5",3,3
"2017/7/6",5,8
"2017/7/7",2,10

PDF出力

ReactのSSR(Server Side Rendering)機能とvtecxのPDF出力機能を組み合わせることで動的にPDFを生成することができます。vtecxapi.toPdf(pages,html,outfilename)は、指定したパラメータを元にPDFを生成します。パラメータの意味は以下の通りです。

  • pagesには出力するPDFのページ数を指定
  • htmlにはPDFの生成元となるHTMLを指定(テンプレートHTML)
  • outfilenameにはPDFのファイル名を指定

vtecxblankプロジェクトには以下のサンプルプログラムがあります。
デプロイ後にログインして /s/ssr.pdfにアクセスすると、「Hello, Harper Perez」と表示されたpdfがダウンロードされます。
/pdf/pdfstyles.tsはPDFのレイアウト等を指定するスタイルシートファイルです。以下のようにstyles属性に指定することで色やサイズなど様々なスタイルを設定することができます。詳しくは、「PDFスタイルシート」を参照してください。

サーバサイドJavaScript(/server/ssr.pdf.tsx)

import * as vtecxapi from 'vtecxapi'
import * as React from 'react'
import * as ReactDOMServer from 'react-dom/server'
import * as pdfstyles from '../pdf/pdfstyles'

interface User {
    firstName: string
    lastName: string
}

function formatName(user: User) {
    return user.firstName + ' ' + user.lastName
}

const user: User = {
    firstName: 'Harper',
    lastName: 'Perez'
}

const element = (
    <html>
        <body>
            <div className="_page" style={pdfstyles._page}>
                <table style={pdfstyles._table}>
                    <tr>
                        <td>
                            <p> Hello, {formatName(user)}! </p>
                        </td>
                    </tr>
                </table>
            </div>
        </body>
    </html>
)

const html = ReactDOMServer.renderToStaticMarkup(element)

// PDF出力
vtecxapi.toPdf(1, html, 'test.pdf')

PDF用スタイルファイル(/pdf/pdfstyles.ts)

export const _page: any = {
    pagesize: 'A4',
    orientation: 'portrait'
}

export const _table: any = {
    bgcolor: '#FFEC8B',
    frame: 'box',
    cellspacing: '3',
    cellpadding: '3',
    width: '90%',
    align: 'left'
}

メール送信

vtecxapi.sendMail(entry: any, to: string[] | null, cc?: string[], bcc?: string[], attachments?: string[])によりメールを送信することができます。
パラメータには以下を指定します。

  • entry
    • title : メールのタイトル
    • summary : テキストメール本文(必須)
    • content : HTMLメール本文(任意、インライン画像を指定可能)
  • to : 送信先アドレス (複数指定の場合配列で指定)
  • cc : CCでの送信先アドレス (配列で指定)
  • bcc : BCCでの送信先アドレス (配列で指定)
  • attachments : 添付ファイル(コンテンツのキーを複数指定可能)

entryのsummaryにはテキストメール本文を、contentにはHTMLメール本文を指定します。HTMLメールを送信できない場合はテキストメールが送信されます。
HTMLメールのインライン画像は <img src="CID:/_html/img/ajax-loader.gif">のように、CIDに続けてキーのURLを指定します。
以下はメールを送信するvtecxblankのサンプルプログラムです。

/server/sendmail.tsx

import * as vtecxapi from 'vtecxapi'

const mailentry = {
    'entry': {
        'title': 'sendmail テスト',
        'summary': 'hello text mail',
        'content': {
            '______text': '<html><body>hello html mail <img src="CID:/_html/img/ajax-loader.gif"></body></html>'
        }
    }
}

const to = ['xxxx@xxx']
const cc = ['xxxx@xxx']
const bcc = ['xxxx@xxx']
const attachments = ['/_html/img/vtec_logo.png']

vtecxapi.sendMail(mailentry, to, cc, bcc, attachments)

これを実行するには、あらかじめpropertiesにメール送信のための設定を行う必要があります。/_settings/properties.xmlに直接記述するか管理画面の「メール|詳細設定」から設定してください。

送信元の指定方法

_mail.from={送信元アドレス}
_mail.from.personal={送信者名}
_mail.password={認証アカウントのパスワード}
_mail.transport.protocol={送信プロトコル}   // "smtp"または"smtps"(Gmailはこちら)。デフォルトは"smtp"。
_mail.smtp.host={SMTPサーバ}
_mail.smtp.port={SMTPポート番号}
_mail.smtp.auth={認証する場合true(デフォルトはtrue)}
_mail.smtp.starttls.enable={STARTTLSを利用する場合true(デフォルトはtrue)}

以下はgmailで送信するための設定例です。

_mail.from=foo@gmail.com
_mail.from.personal=foo
_mail.password=xxx
_mail.transport.protocol=smtps
_mail.smtp.host=smtp.gmail.com
_mail.smtp.port=587
_mail.smtp.auth=true

認証付きリンク

送信するメール本文(テキストメールおよびHTMLメール)に以下の文字列が設定されている場合に認証トークンに変換します。例えば、メールの本文に${RXID=Key}を挿入することで、RXIDを含むURLが自動的に組み立てられます。RXIDは送信先のアカウントのものが生成されます。Keyにはコンテキストパスまで自動的に付加されるためそれ以降のパスを指定してください。

  • ${URL} : URLに変換
  • ${RXID=/Key} : キーと送信先メールアドレスのRXIDをつけたURLを組み立てて変換
  • ${LINK=/Key} : キーと送信先メールアドレスのリンクトークンをつけたURLを組み立てて変換
挿入文: ${RXID=/setpass.html}&value=abc
実際のメール本文: https://test.vte.cx/setpass.html?_RXID=xxx&value=abc

また、変換に際して以下のようなルールがあります。

  • 変換は送信先が1件の場合のみ。送信先を複数指定している場合はブランクに変換さる
  • 送信先メールアドレスがそのサービスでユーザ登録されていない場合はブランクに変換される。(※正確にはメールアドレスからアカウント使用可能文字だけ取り出し、アカウントを検索しています)
  • キー指定部分(/Key)に#が設定されている場合は送信先ユーザのUIDに変換される。

メール受信

vtecxapi.getMail(settings)によりメールを受信することができます。受信結果はfeedのentryに格納されます。複数件受信すると複数件のentryが返ります。
feed.entryの各項目には以下のようにメールの情報がセットされます。()が対応するメールの情報

  • title(subject)
  • subtitle(cc)
  • summary(メール本文)
  • content(添付ファイル) ※ Base64に変換されます
    • content.type(ファイルの形式)
    • content.src(ファイル名)

vtecxblankのmail受信サンプル(/server/getmail.js)では、/s/getmailにアクセスすると、Yahoo!メールの設定情報を元にメールを受信します。
vtecxapi.getMail(settings)のsettingsパラメータにはメール受信のための設定を入れます。

mail受信サンプル(/server/getmail.js)

import * as vtecxapi from 'vtecxapi'

const settings: { [index: string]: string } = {}

// 基本設定(例:yahooメール)
settings['mail.pop3.host'] = 'pop.mail.yahoo.co.jp'
settings['mail.pop3.port'] = '995'
// タイムアウト設定
settings['mail.pop3.connectiontimeout'] = '60000'
//SSL関連設定
settings['mail.pop3.socketFactory.class'] = 'javax.net.ssl.SSLSocketFactory'
settings['mail.pop3.socketFactory.fallback'] = 'false'
settings['mail.pop3.socketFactory.port'] = '995'

settings['username'] = 'xxxxx@yahoo.co.jp'
settings['password'] = 'xxxxx'

const result = vtecxapi.getMail(settings)
vtecxapi.log(JSON.stringify(result))

BigQuery連携

vtecxapi.postBQ(request,async,tablenames?)によりBiqQueryにデータを登録することができます。(※ BigQuery連携を使うには事前に設定が必要です。詳しくは、システムフォルダと各種設定を参照してください)
requestはエントリスキーマで定義されたJSONオブジェクトになります。第一階層の項目名がテーブル名に対応します。tablenamesに第一階層の項目名とテーブル名を指定することで異なる名前をテーブル名に指定することができます。tablenamesには、tablanames['{エンティティの第一階層名}']='{Bigqueryテーブル名}' を指定します。例えば、エンティティの第一階層名がfooでテーブル名がbarの場合、const tablenames = { foo : 'bar' }になります。BigQueryのスキーマはエントリスキーマから自動的に作成されるため定義する必要はありません。
エントリスキーマ以外では、key (STRING)updated (DATETIME)deleted (BOOL)項目が自動的に登録されます。

vtecxapi.deleteBQ(keys,async,tablenames?)により、BigQueryのデータを削除することができます。これは論理削除であり、実際にはdeletedがtrueのレコードが登録されます。

vtecxapi.getBQ(sql,parent)で登録したデータをJSONとして取得できます。sqlにはBigQueryにおいて実行できるSQL文を指定してください。
また、doResponseBQcsv(sql,filename,header?)により、csvとしてダウンロードできます。
データの更新はなく常に追記される形になるため、sqlでは同じkeyでupdatedが最新かつdeletedがfalseのものを取得するようにしてください。
以下にサンプルコードを示します。

    import * as vtecxapi from 'vtecxapi'

    const reqdata = {
        'feed': {
            'entry': [{
                'foo': { 'bar': 'test', 'baz': 'テスト' },
                'link':
                [{ '___rel': 'self', '___href': '/footest/1' }]
            }
            ]
        }
    }
    
    vtecxapi.postBQ(reqdata,false)    

    // 最新のレコードのみ取得
    const sql = 'select f.key,bar,baz,k.updated from my_dataset.foo as f right join (select key,max(updated) as updated from my_dataset.foo group by key) as k on f.updated=k.updated and f.key=k.key where f.deleted = false'
    const result = vtecxapi.getBQ(sql)
    vtecxapi.log(JSON.stringify(result))
        
    const keys = ['/footest/1']
    vtecxapi.deleteBQ(keys,true)    
    

ちなみに、エントリスキーマ(/_settings/template)は以下の通りです。これがBigQueryのスキーマとしても使われます。(テーブル=foo、項目=bar,baz)

foo
 bar
 baz

バッチジョブ

サーバサイドJavaScriptをバックグラウンドで実行したい場合にはバッチジョブ機能が使えます。バッチジョブ機能は対象サービスのプロパティ(/_settings/propertiesエントリーのrights)に以下を設定することで動作させることができます。

_batchjob.{ジョブ名}={分} {時} {日} {月} {曜日} {サーバサイドJS名}
  • {ジョブ名}はサービス内で一意とする。(重複分のジョブは取得されず実行されない。)
  • {分} {時} {日} {月} {曜日}の部分はlinuxのcrontabコマンドと同じ指定方法。
  • {サーバサイドJS名}はリクエストで/s/の後に指定する値。(/_html/server/、.jsを含まない。)

以下は毎朝8:30にメール送信するサーバサイドJS(/server/send-mail.js)を実行する設定の例です。

_batchjob.sendmail=30 8 * * * send-mail

バッチジョブ管理テーブルを参照することでバッチジョブの実行結果を確認できます。これはジョブの実行ごとに登録されるため、ジョブの実行ステータスを記録するログとして扱うことができます。

  • /_batchjob/{ジョブ名}/{ジョブ実行時刻(yyyyMMddHHmm)}のエントリ
  • 上記エントリのtitleにジョブ実行ステータス
  • subtitleにPod名

ジョブ管理ステータス

  • waiting : 実行待ち
  • running : 実行中
  • succeeded : 成功
  • failed : 失敗

waiting(実行待ち)のバッチジョブはプロパティの設定を削除することでキャンセルできますが、running(実行中)のバッチジョブはキャンセルできません。

サーバサイドJSタイムアウト設定

対象サービスのプロパティ(/_settings/propertiesエントリーのrights)に以下を設定することで各サービスにおけるサーバサイドJSのタイムアウト設定ができます。ただしこの設定はproductionサービスのみ有効となります。stagingサービスや設定がない場合などではデフォルトの値(300秒)が採用されます。

  • APサーバからの実行の場合: _javascript.exectimeout={タイムアウト秒}
  • バッチジョブ実行の場合: _javascript.batchjobtimeout={タイムアウト秒}

vtecxapiメソッド一覧

vtecxapiのメソッドには以下のようなものがあります。

リクエスト情報取得

メソッド 説明
getRequest(): any リクエストオブジェクト(feed.entry[0] ~ feed.entry[n])を取得する
getPathinfo(): string PATHINFO(リクエストURLのパス)を取得する
getQueryString(param?: string): string URLパラメータ(クエリストリング)を取得する
httpmethod(): string HTTPメソッド(GET,POST,PUT,DELETE)を取得する
getUriAndQueryString(): string PATHINFO+クエリストリングを取得する
getContentType(): string Content Typeを取得する
getHeaders(): any リクエストヘッダを取得する
getCookies(): any Cookieを取得する
getHeaders(): any リクエストヘッダを取得する
uid(): number uidを取得する
getSettingValue(key: string): string keyを指定してサービス設定情報を取得する
getRemoteIP(): string 送信元のIPアドレスを取得する

データ操作

メソッド 説明
getEntry(url: string): any Entryを取得する。キーとクエリパラメータを指定する
getFeed(url: string, force?: boolean): any Feedを取得する。キーとクエリパラメータを指定する。rightsに文字列がある場合、次ページ(nextpagelink)が存在することを示す。p={nextpagelink}で次ページを取得できる。forceがtrueで全件取得
count(url: string): number 件数を取得する。キーとクエリパラメータを指定する
post(request: any, url: string, force?: boolean): any 親フォルダurlを指定してrequest(feed)をPOSTする。force:1000件以上登録
put(request: any, isbulk?: boolean, parallel?: boolean, async?: boolean): any request(feed)をPUTする。isbulk:1000件以上、parallel:並列実行、async:非同期実行
deleteEntry(url: string, revision?: number): void urlおよびrevisionのentryを削除する
deleteFolder(url: string): any urlとその配下のentryを削除する
saveFiles(props: any): void props(Map)に指定されたファイル名でアップロードファイルを保存する
getHtml(url: string): string 指定されたurlのHTMLを取得する
getContent(url: string): string 指定されたurlのコンテンツを取得する
getCsv(header: string[], items: string[], parent: string, skip: number, encoding: string): any アップロードされたCSVをJSONオブジェクトに変換する
adduserByAdmin(feed: any): any 管理者権限でユーザを追加する

他サイトへのアクセス

メソッド 説明
urlfetch(url: string, method: string, reqData?: string, headers?:any): any 指定されたurlに対してHTTPメソッド(method)を実行する。戻り値は{ status、headers、data }のJSON形式

採番

メソッド 説明
allocids(url: string, num: number): any 指定された採番数(num)だけ採番する

採番カウンタ操作

メソッド 説明
setids(url: string, num: number): void urlの採番カウンタをnumにセットする
addids(url: string, num: number): any urlの採番カウンタの値を加算(+num)する
rangeids(url: string, range: string): void uriの採番カウンタに範囲を指定する(value=start-end)

レスポンス関連

メソッド 説明
setStatus(status_code: number): void レスポンスにステータスコードnumberをセットする
setHeader(name: string, value: string): void レスポンスにレスポンスヘッダをセットする
sendRedirect(location: string): void リダイレクトを実行する
sendError(status_code: number, message?: string): void ステータスコードとメッセージを送信する(HTTPプロトコル)
sendMessage(status_code: number, message: string): void ステータスコードとメッセージを送信する(JSON)
doResponse(feed: any, status_code?: number): void feed.entry[0] ~ feed.entry[n]をレスポンスする。ステータスコードstatus_codeを指定可能
doResponseHtml(html: string): void htmlをレスポンスする
doResponseCsv(value: string[], filename: string): void csvファイルをレスポンスする
getStatus(): number ステータスコードを取得する
RXID(): string RXIDを取得する

ログ

メソッド 説明
log(message: string, title?: string, subtitle?: string): void ログに記録する

PDF、XLS出力

メソッド 説明
toPdf(data: any, html: string, outfilename: string, baseurl?: string): void PDFを出力する。baseurlに合成するPDFファイルを指定可
toXls(data: any, inputxls: string, outfilename: string): void XLSを出力する

メール送受信

メソッド 説明
sendMail(entry: any, to: string[] null, cc?: string[], bcc?: string[], attachments?: string[]): void メールを送信する
getMail(settings: any): any メールを受信する

セッション関連

メソッド 説明
setSessionFeed(name: string, feed: any): void feedをセッションに登録する
setSessionEntry(name: string, entry: any): void entryをセッションに登録する
setSessionString(name: string, str: string): void 文字列をセッションに登録する
setSessionLong(name: string, num: number): void 数値をセッションに登録する
getSessionFeed(name: string): any セッションからfeedを取得する
getSessionEntry(name: string): any セッションからentryを取得する
getSessionString(name: string): string セッションから文字列(string)を取得する
getSessionLong(name: string): number セッションから数値を取得する
deleteSessionFeed(name: string): void セッションにあるfeedを削除する
deleteSessionEntry(name: string): void セッションにあるentryを削除する
deleteSessionString(name: string): void セッションにある文字列(string))を削除する
deleteSessionLong(name: string): void セッションにある数値を削除する
incrementSession(name: string, num: number): void セッションにある数値をnumだけ加算する

ページネーション

メソッド 説明
pagenation(url: string, num: number): void ページIndexを作成する
getPage(url: string, num: number): any num番目のページを取得する

BigQuery連携

メソッド 説明
postBQ(request: any, async: boolean, tablenames?:any): void BigQueryに対してデータを登録する。tablenamesを指定することで異なるテーブルに登録できる。
deleteBQ(keys: string[], async: boolean, tablenames?:any): void BigQueryのデータを削除する(論理削除)。tablenamesを指定することで異なるテーブルに登録できる。
getBQ(sql: string,parent?: string): any BigQueryのデータを取得する。parentを指定すると実行結果はparentの子要素になる
doResponseBQcsv(sql: string,filename: string,header?: string):void BigQueryのデータをcsvでダウンロードする。ファイル名やヘッダを指定できる。

PDFスタイルシート

ページ構造

PDFのページは以下のように3つのレベル要素により構成されます。
第一レベル要素はすべて左下点を基準とする絶対座標で指定することができます。
第二レベル要素は第一レベル要素からの相対位置座標となります。

  • 基底要素
    • htmlタグ:<html>
    • bodyタグ:<body>
    • ページタグ:<div class="_page">
  • 第一レベル要素
    • テーブル:<table> <tr> <td>
      • 注:<table>の子要素に<table>を指定することはできません。
    • イメージ:<img>
    • 図形:<div class="_rectangle"><div class="_line">
  • 第二レベル要素
    • ブロックレベル要素:<div><p>
      • リスト:<ul><ol>
    • インライン要素:<span>
      • リンク:<a>
      • イメージ:<img>
      • 改行:<br>

pageタグ

<div class="_page">タグ(以下、pageタグとします)には、ページの大きさや向き、余白サイズ、暗号化や署名などを設定できます。styleで指定できる属性には以下のものがあります。

プロパティ 内容 初期値 指定方法
pagesize ページサイズ A4 A0~A10, B0~B5, HAGAKI, NOTE, LEGAL, ARCH_E, ARCH_D, ARCH_C, ARCH_B, ARCH_A, FLSA, FLSE, HALFLETTER, _11X17, LEDGER のいずれか。
orientation ページの向き portrait portrait(縦長)かlandscape(横長)のいずれか。
left, right, top, bottom 左、右、上、下の余白 36 数値
nodata ページ制御の際、オフセットはカウントされるがエンティティのインデックスはカウントされない。 (なし) "nodata"のみ記述
footer ページ数表示 false true/false
fontsize ページ全体のデフォルト文字サイズ。ただし、p、span、div、chunk、vchunk、paragraph、a、liタグに囲まれた文字のみ適用される。tdタグに記述された文字列には適用されない。 12 数値
color ページ全体のデフォルト文字色。ただし、p、span、div、chunk、vchunk、paragraph、a、liタグに囲まれた文字のみ適用される。tdタグに記述された文字列には適用されない。 #000000 #xxxxxx
linecolor ページ全体のデフォルト罫線色 #000000 #xxxxxx
font ページ全体のデフォルトフォント HeiseiKakuGo-W5 フォント名(HeiseiKakuGo-W5,HeiseiMin-W3,KozMinPro-Regularのうちいずれか )
title PDF文書のタイトル (なし) 文字列
author PDF文書の作成者 (なし) 文字列
subject PDF文書のサブタイトル (なし) 文字列
keywords PDF文書のキーワード (なし) 文字列
encryption 暗号化 (なし) 40:「40-bit RC4」で暗号化、128:「128-bit RC4」で暗号化 パスワードおよび文書に関する制限(allowで始まる属性)を指定した場合暗号化される。このとき本項目を指定していない場合は128。
password 文書を開くパスワード(PDFファイルを開く(参照する)際に入力するパスワード。) (なし) 文字列
ownerpassword 権限パスワード(PDFファイルのセキュリティ設定を変更する際に入力するパスワード。) (なし) 文字列
allowprinting 印刷 true true:印刷可、false:印刷不可
allowmodifycontents 文書の変更 true true:文書編集可、false:文書編集不可
allowassembly 文書アセンブリ ページの挿入/削除/回転、しおりとサムネールの作成の可否。 false true:変更可、false:変更不可
allowcopy 内容のコピーと抽出 true true:コピー可、false:コピー不可
allowscreenreaders アクセシビリティのための内容の抽出 視覚に障碍を持つユーザに対して、スクリーンリーダ(読み上げ)の利用可否。 true true:可、false:不可
allowmodifyannotations 注釈、フォームフィールドの入力および署名 false true:編集可、false:編集不可
allowfillin フォームフィールドの入力および署名 false true:入力可、false:入力不可 注)文書パスワードを付けた場合のみ有効。allowmodifyannotationsが編集可(true)の場合、trueとなる。

<table>タグ

<table>タグにより表を作ることができます。これは、行(<tr>)と列(<td>もしくは<th>)を子要素にもちます。style属性には以下を指定することができます。

プロパティ 内容 初期値 指定方法
cols 列数 1 数値
width 幅の縮尺 80% 固定の数値、またはパーセント指定
cellpadding 全体の縦の間隔 0 数値
cellspacing セル同士の間隔 0 数値
frame 罫線 void void,above,below,hsides,vsides,lhs,rhs,box,border のいずれか。外枠のみの指定であり、内側の線はtdタグの属性で指定。
border 枠線の幅 1 数値
bordercolor 枠線の色 ページで指定されたデフォルト罫線色 #xxxxxx
bgcolor 背景色 #FFFFFF #xxxxxx
align 表示位置 center center, left, right のいずれか
widths 各列の幅(どれだけのカラムを割くか) (なし) 列数分、割合をカンマでつないで指定。例)width: 3,4,4;
absolutex, absolutey ページ内の絶対座標 (なし) ページ左下を基点とし、テーブルの右上角の座標を指定。
font テーブルのデフォルトフォント名 (なし) フォント名。"$" + エンティティの項目名を指定することで、エンティティの内容を適用できる。
size テーブルのデフォルト文字サイズ (なし) 数値
style テーブルのデフォルト文字スタイル (なし) bold(太字),italic(斜体),underline(下線),strikethru(取消線) 複数指定の場合カンマでつなぐ。
color テーブルのデフォルト文字色 (なし) #xxxxxx

<tr>タグ

style属性には以下を指定できます。

プロパティ 内容 初期値 指定方法
font 行のデフォルトフォント名 (なし) フォント名
size 行のデフォルト文字サイズ (なし) 数値
style 行のデフォルト文字スタイル (なし) bold(太字),italic(斜体),underline(下線),strikethru(取消線) 複数指定の場合カンマでつなぐ。
color 行のデフォルト文字色 (なし) #xxxxxx

<td>タグ

style属性には以下を指定できます。

プロパティ 内容 初期値 指定方法
align セル内データの横方向の配置 left left,center,right,justifyall(均等割付) のいずれか
valign セル内データの縦方向の配置 top top,middle,bottom,baseline のいずれか
colspan 結合する列数 1 数値
rowspan 結合する行数 1 数値
bordercolor セルの枠線の色 ページで指定されたデフォルト罫線色 #xxxxxx
bgcolor セルの背景色 #FFFFFF #xxxxxx
height セルの高さ。設定値が最低限保証され、文字の折り返しなどで超える場合はこれ以上の高さとなる。 (なし) 数値
leading 文字の改行ピッチ 16 セル上枠と文字下部の距離を数値で指定。
borderwidth セルの罫線の太さ 1 数値
offsetx, offsety セル内の表示開始座標 (なし) セル左下を基点とし、表示内容の左下の座標を指定。
space 表示文字列の文字間隔 0 数値
roundrighttop, roundrightbottom, roundlefttop, roundleftbottom セルの右上、右下、左上、左下の角を丸める false true/false
roundr セルの角を丸める際の曲率 1 数値
lefttoprightbottom, righttopleftbottom セルの左上から右下、右上から左下へ斜線を引く false true/false
linehscale, linevscale セルの罫線を横、縦方向に拡大・縮小 1 中心からの倍率を指定。拡大時は1より大きな数値、縮小時は0.x。
doubleline セルの罫線を二重線にする false true/false
left, right, top, bottom 左、右、上、下の枠線 false true/false
nowrap 改行しない。セルに収まる分のみ表示される。 false true/false
font セルのデフォルトフォント名 (なし) フォント名。"$" + エンティティの項目名を指定することで、エンティティの内容を適用できる。
size セルのデフォルト文字サイズ (なし) 数値
style セルのデフォルト文字スタイル (なし) bold(太字),italic(斜体),underline(下線),strikethru(取消線) 複数指定の場合カンマでつなぐ。
color セルのデフォルト文字色 (なし) #xxxxxx

文字列の表示

文字列を表示する場合、<p>,<div>,<span>のいずれかのタグを使用します。
上記タグで囲まなければ、デフォルトを含むレイアウト表示はされませんのでご注意ください。
<p>,<div>タグの場合、文字列表示の後に改行されます。<span>タグの場合改行されません。style属性には以下を指定できます。

プロパティ 内容 初期値 指定方法
font フォント セル、行、テーブル、ページで指定されたデフォルトフォント フォント名。
size 文字サイズ セル、行、テーブル、ページで指定されたデフォルト文字サイズ 数値
style 文字編集 セル、行、テーブルで指定された文字スタイル bold(太字),italic(斜体),underline(下線),strikethru(取消線) 複数指定の場合カンマでつなぐ。vertical:trueの場合指定しないこと。
color 文字の色 セル、行、テーブル、ページで指定されたデフォルト文字色 #xxxxxx
nowrap 改行しない。セルを超えて表示される。 false true/false
hscale 文字の横幅割合 1 1を基準とした割合。縮めるなら0.x、広げるなら1より大きな数字。
vertical 縦書き false true/false
offsetx, offsety セル内の文字列表示開始座標 (なし) セル左下を基点とし、表示内容の左下の座標を指定。

改行

改行は<br/>タグを使用します。(必ず最後にスラッシュを入れて閉じてください)

リンク

リンクは<a>タグを使用します。href属性にリンク先URLを指定してください。
<a>タグに囲む文字列は、<p>,<div>,<span>のいずれかで囲んでください。

リスト

リストは<ul><ol><li>タグを使用します。
<li>タグに囲む文字列は、<p>,<div>,<span>のいずれかで囲んでレイアウトを編集してください。シンボルは<ul><ol>タグでレイアウト編集できます。style属性には以下を指定できます。

プロパティ 内容 初期値 指定方法
type ulの場合固定文字。olの場合項目番号。 ulの場合 - 。olの場合 1 。 ulの場合任意の一文字。olの場合、1, A, a のいずれか。
start olの場合に使用。項目番号の開始文字。 1, A, a のいずれか type未指定もしくは1の場合数字。typeがA, a の場合開始したいアルファベット。
symbolindent 数値(11.0ぐらいをデフォルトでセットしないと重なってしまう) (なし) true/false
font シンボルのフォント ページで指定されたデフォルトフォント フォント名
size シンボルのフォントサイズ ページで指定されたデフォルトフォントサイズ 数値
style シンボルのスタイル (なし) bold(太字),italic(斜体),underline(下線),strikethru(取消線) 複数指定の場合カンマでつなぐ。
color シンボルの文字色 ページで指定されたデフォルト文字色 #xxxxxx
leading 文字の改行ピッチ 16 セル上枠と文字下部の距離を数値で指定。

画像

画像は<img>タグを使用します。src属性に画像のurl、width属性に画像の幅、height属性に画像の高さを指定してください。style属性には以下を指定できます。

プロパティ 内容 初期値 指定方法
plainwidth 画像の幅。widthより優先される。 (なし) 数値
plainheight 画像の高さ。heightより優先される。 (なし) 数値
rotation 回転 0 数値
absolutex, absolutey ページ内の絶対座標 (なし) ページ左下を基点とし、画像の左下角の座標を指定。

<div class="_line">で線を描画します。style属性には以下を指定できます。

プロパティ 内容 初期値 指定方法
linewidth 線の幅 1 数値
color 線の色 #000000 #xxxxxx
x1, y1 開始点の座標 (なし) 数値
x2, y2 終了点の座標 (なし) 数値
linedushon 描画する線を破線にする場合、表示部分の長さを設定 1 数値
linedushoff 描画する線を破線にする場合、非表示部分の長さを設定 0 数値

四角形

<div class="_rectangle">で四角形を描画します。style属性には以下を指定できます。

プロパティ 内容 初期値 指定方法
linewidth 線の幅 1 数値
width 四角形の幅 0 数値
height 四角形の高さ 0 数値
color 四角形の線の色 #000000 #xxxxxx
absolutex, absolutey ページ内の絶対座標 (なし) ページ左下を基点とし、四角形の左下角の座標を指定。
linedushon 描画する線を破線にする場合、表示部分の長さを設定 1 数値
linedushoff 描画する線を破線にする場合、非表示部分の長さを設定 0 数値

角丸四角形

<div class="_roundrectangle">で四角形を描画します。style属性には以下を指定できます。

プロパティ 内容 初期値 指定方法
linewidth 線の幅 1 数値
width 四角形の幅 0 数値
height 四角形の高さ 0 数値
roundr 角の曲率 1 数値
color 四角形の線の色 #000000 #xxxxxx
absolutex, absolutey ページ内の絶対座標 (なし) ページ左下を基点とし、四角形の左下角の座標を指定。
linedushon 描画する線を破線にする場合、表示部分の長さを設定 1 数値
linedushoff 描画する線を破線にする場合、非表示部分の長さを設定 0 数値

<div class="_circle">で円を描画します。style属性には以下を指定できます。

プロパティ 内容 初期値 指定方法
linewidth 線の幅 1 数値
absolutex, absolutey ページ内の絶対座標 (なし) ページ左下を基点とし、円の左下角の座標を指定。
radius 円の半径 (なし) 数値
linedushon 描画する線を破線にする場合、表示部分の長さを設定 1 数値
linedushoff 描画する線を破線にする場合、非表示部分の長さを設定 0 数値
color 円の線の色 #000000 #xxxxxx

バーコードJAN(EAN、UPC)規格

<div class="_barcodeEAN">でJAN(EAN、UPC)規格のバーコードを描画します。style属性には以下を指定できます。

プロパティ 内容 初期値 指定方法
value バーコードに表示する値 4512345678901 数字
height 高さ 30 数値
size 文字のサイズ 10 数値
width 0.75 数値
font フォント名 (なし) 文字を表示しない場合、"null"を指定する。

バーコードNW-7(CODABAR)規格

<div class="_barcodeNW7">でNW-7(CODABAR)規格のバーコードを描画します。style属性には以下を指定できます。

プロパティ 内容 初期値 指定方法
value バーコードに表示する値 4512345678901 文字列
height 高さ 30 数値
size 文字のサイズ 10 数値
width 0.75 数値
startstop スタートストップ文字の有無 true true/false
font フォント名 (なし) 文字を表示しない場合、"null"を指定する。

バーコードcode39規格

<div class="_barcode39">でcode39規格のバーコードを描画します。style属性には以下を指定できます。

プロパティ 内容 初期値 指定方法
value バーコードに表示する値 4512345678901 文字列
height 高さ 30 数値
size 文字のサイズ 10 数値
width 0.75 数値
startstop スタートストップ文字の有無 true true/false
extended 拡張 true true/false
font フォント名 (なし) 文字を表示しない場合、"null"を指定する。

バーコードcode128規格

<div class="_barcode128">でcode128規格のバーコードを描画します。style属性には以下を指定できます。

プロパティ 内容 初期値 指定方法
value バーコードに表示する値 4512345678901 文字列
height 高さ 30 数値
size 文字のサイズ 10 数値
width 0.75 数値
codetype コードタイプ (なし) "UCC"(CODE128_UCC規格)または"RAW"(CODE128_RAW規格)を指定。それ以外はCODE128規格。
font フォント名 (なし) 文字を表示しない場合、"null"を指定する。

QRコード

<div class="_qrcode">でQRコードを描画します。style属性には以下を指定できます。

プロパティ 内容 初期値 指定方法
value バーコードに表示する値 4512345678901 文字列
height 高さ 30 数値
width 0.75 数値
version 型番(シンボルの大きさ) 0 0~10を指定
errorcorrectionlevel 誤り訂正レベル "H" "L"(コード語の約7%を復元可能), "M"(15%), "Q"(25%), "H"(30%)のいずれかを指定
cellsize セルのサイズ(pixel) 1 1~4を指定
margin 余白(pixel) 0 0~32を指定

HTTPステータスとメッセージ

ステータスコード メッセージ 意味 解説
200 OK. 成功 処理が正しく実行された
201 Created. 生成 リソースが新しく生成された
202 Accepted. 受付 async処理の場合、またはdeleteserviceの戻り値
204 No entry. コンテンツなし リソースがなかった
206 Partial Content. 部分的内容 返された結果が一部である
400 Request object is invalid. リクエスト不正 不正なリクエストが送信された
401 Authentication error. 認証エラー 認証に失敗
403 Access denied. 認可エラー 認証は成功しているが認可でエラー
404 No entry. 不存在 コンテンツが見つからなかった
405 Method Not Allowed. 不許可 productionサービスにおいてhttps以外でアクセスしてきた場合。またはstagingサービスにおいてhttp以外でアクセスしてきた場合
406 Authentication time out. 認証タイムアウト 認証タイムアウトが発生した
409 Conflict 競合発生 キー重複、もしくは楽観的ロック失敗
412 The signature is invalid. 署名検証エラー 署名の検証において失敗した(署名が不正)
413 Payload Too Large. リクエストサイズエラー リクエストデータのサイズ超過
417 Request security error. リクエストセキュリティエラー XMLHttpRequestからのリクエストではないがJSONで出力しようとしている
424 Not in service. サービスを利用不可 サービスを利用できないかサーバサイドJavaScriptでエラーが発生した
426 Upgrade Required. 設定不正 サービスでの設定内容が不正
500 INTERNAL_SERVER_ERROR 内部サーバーエラー サーバにおける致命的エラー

400エラー詳細

メッセージ 意味
XX is required. XXの指定が必要
XX is not available. XXが使えない
XX does not exist. XXが存在しない
XX is invalid. XXが不正
Allocate id must be a numeric value. 正しい数値がIDに指定されていない
Callback strings must use alphanumeric characters. callbackには英数字以外使用不可
Duplicated Link self. Link selfが重複している
Duplicated rules for ACLs. 既に同じ権限が設定されている
Duplicated URIs for URIが重複している
Forbidden request to this service. このサービスへの許可されないリクエスト
Max must be greater than min. 最大値が最小値より大きい値ではない
Must specify a 'E'(External) control. E権限を指定する必要がある
Not allowed to cancel the process. プロセスをキャンセルできない
Not allowed to use an alias for bulkcopy. bulkcopyではaliasは使えない
Optimistic locking failed for the specified template. 指定したテンプレートの更新エラー(楽観的排他エラー)が発生
Password must be contain at least 8 characters, including at least 1 number and includes both lower and uppercase letters. passwordは1文字以上で数字と小文字と大文字混じりである必要がある
Request format is invalid: XX リクエストのフォーマットが正しくセットされていない
Revision number must be a numeric value. リビジョンが数字ではない
Specified value is out of range. リビジョンの値が範囲外
Specified URI does not match the id nor key. 指定したURIがIDとKeyに一致しない
The first limit must be less than limit(XX). first limitはlimit(XX)以下を指定しなければならない
The first limit must be more than 0. first limitは0以上を指定しなければならない
The number of pages must be more than 0. number of pagesは0以上を指定しなければならない
Too many entities. entityの数が多すぎる
Unauthorized request to modify the auth. Authの変更リクエストは受け付けられない
URI must not contain any prohibited characters. URIに許可していない文字の使用は不可
URI must not contain any white-space characters. URIにblank文字は使えない
URI must start with a slash. URIは/から始まるものでなければならない
Accesskey and Accesstoken can not be used. AccesskeyとAccesstokenが使えない
Please set only one key. selfは1エントリ1件のみ
Please make a pagination index in advance. 先にpagination indexを作成する必要がある
Session is disabled. セッションが無効になっている
Session does not exist. セッションが存在しない
Top entry can not be specified. ルートエントリは指定不可
Forbidden request to this service. このサービスで実行できない
Service init entry is nothing. サービス初期化エントリが存在しない
.js' is not found. .jsファイルが見つからない
Service does not exist. サービスが存在しない
The Web Application has not been activated. Webアプリケーションが有効になっていない

401エラー詳細

メッセージ 意味
Authentication is locked. 認証がロックされている
Authentication time out. 認証タイムアウト
Remote access is not allowed. リモートからのアクセスは禁止
Captcha required at next login. 次回からCaptcha認証が必要

409エラー詳細

メッセージ 意味
Duplicated primary key. Keyが重複している
Alias is duplicated. aliasが重複している
User is already registered. ユーザが既に登録されている
Optimistic locking failed. 更新エラー(楽観的排他エラー)

ページトップへ