開発情報・ナレッジ

投稿者: SPIRERS ナレッジ向上チーム 2022年2月18日 (金)

APIを使ったDB 複製のサンプルプログラム

ver.2 では、まだDB複製機能が実装されていません。(※)
今回は、 DB複製機能を作ってみました。
DBを複製して、どんどんDBを作りたいと思っている方は、ぜひ参考にしてください。
※ 今後、実装される可能性があります。

前提

ver.2 のページ機能にプログラムを設置し、画面上から複製元のアプリIDとDB IDを入力できるようにしています。

PHP / API

PHPの処理を書きます。
[API_KEY]は、各環境に合わせた値の設定をお願いします。
変更が必要な箇所は、API_KEY の部分のみです。

PHP
<?php
define("API_URL", "https://api.spiral-platform.com/v1/");
define("API_KEY", "");
$commonBase = CommonBase::getInstance();

if($SPIRAL->getParam("appID")){
    $SPIRAL->setTHValue("postAction", true);
    $SPIRAL->setTHValue("error", false);
    // DBを取得
    $addData = $commonBase->apiCurlAction("GET","apps/".$SPIRAL->getParam("appID")."/dbs/".$SPIRAL->getParam("dbID"));

    // 名称をチェックして、入力がない場合は、 C_ をつける
    if($SPIRAL->getParam("appName")){
        $newDbData["name"] = $SPIRAL->getParam("appName");
    }else{
        $newDbData["name"] = "c_".$addData["name"];
    }
    if($SPIRAL->getParam("displayName")){
        $newDbData["displayName"] = $SPIRAL->getParam("displayName");
    }else{
        $newDbData["displayName"] = "c_".$addData["displayName"];
    }
	// 複製用DBに説明をセット
    $newDbData["description"] = $addData["description"];
    foreach($addData["fields"] as $key => $val){
		// 取得したDBの情報で複製に不要な項目は削除
        unset($addData["fields"][$key]["id"]);
        unset($addData["fields"][$key]["fieldOrder"]);
        unset($addData["fields"][$key]["size"]);
        if($addData["fields"][$key]["type"] == "reference"){
            $addData["fields"][$key]["referenceDbId"] = $addData["fields"][$key]["referenceDb"]["id"];
            $addData["fields"][$key]["referenceLabelFieldId"] = $addData["fields"][$key]["referenceLabelField"]["id"];
            unset($addData["fields"][$key]["referenceDb"]);
            unset($addData["fields"][$key]["referenceLabelField"]);
        }
    }
	// 複製用DBに複製元の情報をセット
    $newDbData["fields"] = $addData["fields"];
	// DB複製
    $newDB = $commonBase->apiCurlAction("POST","apps/".$SPIRAL->getParam("appID")."/dbs",$newDbData);

    if(isset($newDB["status"]) && $newDB["status"] != 200){
        // エラー時
        $SPIRAL->setTHValue("error", true);
        $SPIRAL->setTHValue("errorLocation", "DB Create");
        $SPIRAL->setTHValue("apiMessage", print_r($newDB, true));
    }
}else{
    $SPIRAL->setTHValue("postAction", false);
}



//------------------------------
// 共通用モジュール
//------------------------------

class CommonBase
{
	/**
	 * シングルトンインスタンス
	 * @var UserManager
	 */
	protected static $singleton;
    

	public function __construct()
	{
		if (self::$singleton) {
			throw new Exception('must be singleton');
		}
		self::$singleton = $this;
	}

	/**
	 * シングルトンインスタンスを返す
	 * @return UserManager
	 */
	public static function getInstance()
	{
		if (!self::$singleton) {
			return new CommonBase();
		} else {
			return self::$singleton;
		}
	}

    
	/**
	 * V2用 API送信ロジック
	 * @return Result
	 */
	function apiCurlAction($method, $addUrlPass, $data = null)
	{
        
		$header = array(
			"Authorization:Bearer " . API_KEY,
			"Content-Type:application/json",
			"X-Spiral-Api-Version: 1.1",
		);

		// curl
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($curl, CURLOPT_URL, API_URL . $addUrlPass);
		curl_setopt($curl, CURLOPT_HTTPHEADER, $header);

		if ($method == "POST") {
			curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
			curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
		}
		if ($method == "PATCH") {
			curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
			curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
		}
		if ($method == "DELETE") {
			curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
		}
		if ($method == "GET") {
			curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
		}
		$response = curl_exec($curl);
		if (curl_errno($curl)) echo curl_error($curl);
		curl_close($curl);

		return json_decode($response, true);
	}
}

HTML(Thymeleaf)

アプリIDとDB IDを入力するための画面 と アプリ複製の処理結果を表示させる処理を記載

HTML(Thymeleaf)
<div class="box_con">
    <h1>ver.2 DB 複製ツール</h1>
    <form method="post" action="#">
    <table class="formTable">
        <tr>
            <th>アプリID<span>必須</span></th>
            <td><input size="30" type="text" name="appID" required="required" value="" /></td>
        </tr>
        <tr>
            <th>DB ID<span>必須</span></th>
            <td><input size="30" type="text" name="dbID" required="required" value="" /></td>
        </tr>
        <tr>
            <th>表示名</th>
            <td>
                <input size="30" type="text" name="displayName" maxlength="64" placeholder="未入力の場合、コピー元の表示名の接頭に「c_」がついた表示名になります。" /><br />
                <span class="contents-note">* 既存アプリの表示名と重複不可</span><br />
            </td>
        </tr>
        <tr>
            <th>識別名</th>
            <td>
                <input size="30" type="text" name="appName" pattern="^[0-9A-Z_a-z]+$" maxlength="64" placeholder="未入力の場合、コピー元の識別名の接頭に「c_」がついた識別名になります。" /><br />
                <span class="contents-note">* アルファベット大文字・小文字、数字、アンダースコアのみ使用可能</span><br />
                <span class="contents-note">* 最初の文字はアルファベット</span><br />
                <span class="contents-note">* 既存アプリの識別名と重複不可</span><br />
            </td>
        </tr>
    </table><br />
    <p class="btn">
        <span><input type="submit" value="  確認  " /></span>
    </p>
    </form>
    <div th:if="${!cp.result.isSuccess}">
        <p th:text="${cp.result.errorMessage}">error message</p>
    </div>
    <div class="con_pri" style="margin-top: 20px;">
        <div th:if="${cp.result.value['postAction'] == true}" class="box_pri">
            <div class="box_tori">
                <h4>実行結果</h4>
                <!--/* エラー時 */-->
                <p th:if="${cp.result.value['error']}" th:text="${cp.result.value['errorLocation']}"></p>
                <p th:if="${cp.result.value['error']}" th:text="${cp.result.value['apiMessage']}"></p>    
                <!--/* 成功時 */-->
                <p th:if="${!cp.result.value['error']}" class="txt">success!</p>
            </div>
        </div>
    </div>
</div>

CSS

一応、見やすいようにCSSも追加

CSS
/*リセットcss↓*/
html {
    overflow-y: scroll;
}

body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, fieldset, input, textarea, p, blockquote, th, td {
    margin: 0;
    padding: 0;
}

address, caption, cite, code, dfn, em, strong, th, var {
    font-style: normal;
}

table {
    border-collapse: collapse;
    border-spacing: 0;
}

caption, th {
    text-align: left;
    color: #217599;
}

q:before, q:after {
    content: '';
}

object, embed {
    vertical-align: top;
}

hr, legend {
    display: none;
}

h1, h2, h3, h4, h5, h6 {
    font-size: 100%;
}

img, abbr, acronym, fieldset {
    border: 0;
}

li {
    list-style-type: none;
}

sup {
    vertical-align: super;
    font-size: 0.5em;
}

img {
    vertical-align: top;
}

i {
    font-style: normal;
}

/*----リセットcss*----/

/*デザインcss↓*/
.box_con {
    max-width: 900px;
    margin: 0 auto;
}

@media only screen and (max-width: 768px) {
    .box_con {
        width: 95%;
    }
}

.box_con form {
    width: 100%;
}

.box_con form table {
    width: 100%;
}

.box_con form table tr {
    position: relative;
}

.box_con form table tr:after {
    content: "";
    position: absolute;
    width: 100%;
    left: 0;
    bottom: 0;
    height: 1px;
    border-bottom: dotted #cdcdcd 1px;
}

.box_con form table tr th {
    width: 30%;
    font-weight: normal;
    padding: 1em .5em;
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}

@media only screen and (max-width: 768px) {
    .box_con form table tr th {
        text-align: center;
        width: 100%;
        display: block;
        background: #97ae88;
        padding: .8em .2em;
        color: #fff;
    }
}

.box_con form table tr th span {
    background: #cd6f55;
    padding: 0 .3em;
    color: #fff;
    margin-left: .5em;
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}

.box_con form table tr td {
    padding: 1em .5em;
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}

@media only screen and (max-width: 768px) {
    .box_con form table tr td {
        padding: 1.5em .5em;
        display: block;
        width: 100%;
    }
}

.box_con form table tr .box_br {
    display: block;
}

.box_con form table tr select {
    border: 1px solid #97ae88;
}

.box_con form table tr label input {
    cursor: pointer;
    display: none;
    vertical-align: middle;
}

.box_con form table tr .radio02-input+label {
    padding-left: 23px;
    margin-right: 20px;
    position: relative;
}

.box_con form table tr .radio02-input+label:before {
    content: "";
    display: block;
    position: absolute;
    top: 50%;
    left: 0;
    width: 16px;
    height: 16px;
    border: 1px solid #999;
    border-radius: 50%;
    -moz-transform: translateY(-50%);
    -ms-transform: translateY(-50%);
    -webkit-transform: translateY(-50%);
    transform: translateY(-50%);
}

.box_con form table tr .radio02-input:checked+label:after {
    content: "";
    display: block;
    position: absolute;
    top: 50%;
    left: 3px;
    width: 12px;
    height: 12px;
    background: #97ae88;
    border-radius: 50%;
    -moz-transform: translateY(-50%);
    -ms-transform: translateY(-50%);
    -webkit-transform: translateY(-50%);
    transform: translateY(-50%);
}

.box_con form table tr select, .box_con form table tr input, .box_con form table tr textarea {
    width: 100%;
    height: 3em;
    padding: .5em;
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}

.box_con form table tr textarea {
    height: 10em;
}

/*プライバシーのデザインcss↓*/
.con_pri {
    max-width: 700px;
    margin: 0 auto;
}

@media only screen and (max-width: 768px) {
    .con_pri {
        width: 95%;
    }
}

.con_pri .box_pri {
    height: 300px;
    overflow-y: scroll;
    border: 1px solid #cdcdcd;
    background: #f7f7f7;
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    margin-top: 20px;
    padding: 20px 55px;
}

@media only screen and (max-width: 768px) {
    .con_pri .box_pri {
        margin-top: 4%;
        padding: 3%;
    }
}

@media only screen and (min-width: 769px) and (max-width: 1024px) {
    .con_pri .box_pri {
        padding: 4%;
    }
}

.con_pri .box_pri .box_tori {
    text-align: left;
    margin-top: 40px;
}

@media only screen and (max-width: 768px) {
    .con_pri .box_pri .box_tori {
        margin-top: 4%;
    }
}

.con_pri .box_pri .box_tori h4 {
    font-weight: normal;
    margin-bottom: 30px;
    font-size: 150%;
}

@media only screen and (max-width: 768px) {
    .con_pri .box_pri .box_tori h4 {
        margin-bottom: 4%;
    }
}

.con_pri .box_pri .box_tori .txt {
    padding: 0 20px;
}

@media only screen and (max-width: 768px) {
    .con_pri .box_pri .box_tori .txt {
        padding: 0;
    }
}

.con_pri .box_pri .box_num {
    margin-top: 30px;
}

@media only screen and (max-width: 768px) {
    .con_pri .box_pri .box_num {
        margin-top: 5%;
    }
}

.con_pri .box_pri .box_num h4 {
    font-weight: normal;
    font-size: 113%;
}

.con_pri .box_pri .box_num .txt {
    padding: 10px 0 0 20px;
}

@media only screen and (max-width: 768px) {
    .con_pri .box_pri .box_num .txt {
        padding: 3% 0 0 3%;
    }
}

.box_check {
    text-align: center;
    margin: 1em auto;
}

.box_check label {
    display: inline-block;
}

.box_check label span {
    margin-left: .3em;
}

.btn {
    text-align: center;
}

.btn input {
    display: inline-block;
    background: #eee;
    padding: .5em 4em;
    color: #000;
    text-decoration: none;
    cursor: pointer;
    border: none;
    -moz-transition: all 0.4s;
    -o-transition: all 0.4s;
    -webkit-transition: all 0.4s;
    transition: all 0.4s;
}

.btn input:hover {
    filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=70);
    opacity: 0.7;
}

h1 {
    border-bottom: solid 3px #69d3a6;
    position: relative;
    font-size: 2.75em;
    color: #01a39d;
}

h1:after {
    position: absolute;
    content: " ";
    display: block;
    border-bottom: solid 3px #1aa3bf;
    bottom: -3px;
    width: 20%;
}

.contents-note {
    font-size: 0.9em;
    color: #777;
}

無理やり作ってしまっているところもあるので、DBの複製後はしっかりチェックをお願いいたします。
不具合やほかのやり方がある等あれば、下記の「コンテンツに関しての要望はこちら」からご連絡ください。

解決しない場合はこちら コンテンツに関しての
要望はこちら