Refactor API

This commit is contained in:
grandeljay 2022-11-11 13:34:40 +01:00
parent c5c2893201
commit 29c9c3c472
20 changed files with 349 additions and 151 deletions

View file

@ -144,25 +144,11 @@ $locale = isset($_REQUEST['locale']) ? $_REQUEST['locale'] : \Locale::lookup($lo
*/ */
Wish::initialize(); Wish::initialize();
/**
* API
*/
if (isset($api)) {
return;
}
/** /**
* Pretty URLs * Pretty URLs
*/ */
$url = new URL($_SERVER['REQUEST_URI']); $url = new URL($_SERVER['REQUEST_URI']);
/**
* Install
*/
if (!$options || !$options->getOption('isInstalled')) {
$page = 'install';
}
/** /**
* Database Update * Database Update
*/ */

View file

@ -8,13 +8,13 @@
namespace wishthis; namespace wishthis;
ob_start(); global $page;
$api = true; if (!isset($page)) {
http_response_code(403);
die('Direct access to this location is not allowed.');
}
require '../../index.php';
$response = array();
$dateFormatter = new \IntlDateFormatter( $dateFormatter = new \IntlDateFormatter(
$_SESSION['user']->getLocale(), $_SESSION['user']->getLocale(),
\IntlDateFormatter::MEDIUM, \IntlDateFormatter::MEDIUM,
@ -43,9 +43,3 @@ switch ($_SERVER['REQUEST_METHOD']) {
$response['html'] = $html; $response['html'] = $html;
break; break;
} }
$response['warning'] = ob_get_clean();
header('Content-type: application/json; charset=utf-8');
echo json_encode($response);
die();

View file

@ -8,13 +8,12 @@
namespace wishthis; namespace wishthis;
ob_start(); global $page, $database;
$api = true; if (!isset($page)) {
http_response_code(403);
require '../../index.php'; die('Direct access to this location is not allowed.');
}
$response = array();
switch ($_SERVER['REQUEST_METHOD']) { switch ($_SERVER['REQUEST_METHOD']) {
case 'POST': case 'POST':
@ -36,9 +35,3 @@ switch ($_SERVER['REQUEST_METHOD']) {
$response['success'] = $success; $response['success'] = $success;
break; break;
} }
$response['warning'] = ob_get_clean();
header('Content-type: application/json; charset=utf-8');
echo json_encode($response);
die();

View file

@ -8,12 +8,12 @@
namespace wishthis; namespace wishthis;
$api = true; global $page, $database;
$response = array();
ob_start(); if (!isset($page)) {
http_response_code(403);
require '../../index.php'; die('Direct access to this location is not allowed.');
}
switch ($_SERVER['REQUEST_METHOD']) { switch ($_SERVER['REQUEST_METHOD']) {
case 'GET': case 'GET':
@ -51,9 +51,3 @@ switch ($_SERVER['REQUEST_METHOD']) {
} }
break; break;
} }
$response['warning'] = ob_get_clean();
header('Content-type: application/json; charset=utf-8');
echo json_encode($response);
die();

View file

@ -8,12 +8,12 @@
namespace wishthis; namespace wishthis;
$api = true; global $page;
$response = array();
ob_start(); if (!isset($page)) {
http_response_code(403);
require '../../index.php'; die('Direct access to this location is not allowed.');
}
switch ($_SERVER['REQUEST_METHOD']) { switch ($_SERVER['REQUEST_METHOD']) {
case 'GET': case 'GET':
@ -28,9 +28,3 @@ switch ($_SERVER['REQUEST_METHOD']) {
} }
break; break;
} }
$response['warning'] = ob_get_clean();
header('Content-type: application/json; charset=utf-8');
echo json_encode($response);
die();

View file

@ -8,14 +8,12 @@
namespace wishthis; namespace wishthis;
$api = true; global $page, $database;
$response = array(
'success' => false,
);
ob_start(); if (!isset($page)) {
http_response_code(403);
require '../../index.php'; die('Direct access to this location is not allowed.');
}
switch ($_SERVER['REQUEST_METHOD']) { switch ($_SERVER['REQUEST_METHOD']) {
case 'GET': case 'GET':
@ -276,9 +274,3 @@ switch ($_SERVER['REQUEST_METHOD']) {
$response['success'] = true; $response['success'] = true;
break; break;
} }
$response['warning'] = ob_get_clean();
header('Content-type: application/json; charset=utf-8');
echo json_encode($response);
die();

View file

@ -8,12 +8,12 @@
namespace wishthis; namespace wishthis;
$api = true; global $page, $database;
$response = array();
ob_start(); if (!isset($page)) {
http_response_code(403);
require '../../index.php'; die('Direct access to this location is not allowed.');
}
switch ($_SERVER['REQUEST_METHOD']) { switch ($_SERVER['REQUEST_METHOD']) {
case 'GET': case 'GET':
@ -57,9 +57,3 @@ switch ($_SERVER['REQUEST_METHOD']) {
} }
break; break;
} }
$response['warning'] = ob_get_clean();
header('Content-type: application/json; charset=utf-8');
echo json_encode($response);
die();

View file

@ -8,12 +8,12 @@
namespace wishthis; namespace wishthis;
$api = true; global $page, $database;
$response = array();
ob_start(); if (!isset($page)) {
http_response_code(403);
require '../../index.php'; die('Direct access to this location is not allowed.');
}
switch ($_SERVER['REQUEST_METHOD']) { switch ($_SERVER['REQUEST_METHOD']) {
case 'POST': case 'POST':
@ -170,9 +170,3 @@ switch ($_SERVER['REQUEST_METHOD']) {
$response['success'] = true; $response['success'] = true;
break; break;
} }
$response['warning'] = ob_get_clean();
header('Content-type: application/json; charset=utf-8');
echo json_encode($response);
die();

View file

@ -18,10 +18,10 @@ $(function() {
*/ */
/** API */ /** API */
$.fn.api.settings.api = { $.fn.api.settings.api = {
'get wishlists' : '/src/api/wishlists.php', 'get wishlists' : '/?page=api&module=wishlists',
'delete wishlist' : '/src/api/wishlists.php', 'delete wishlist' : '/?page=api&module=wishlists',
'update wish status' : '/src/api/wishes.php', 'update wish status' : '/?page=api&module=wishes',
'delete wish' : '/src/api/wishes.php', 'delete wish' : '/?page=api&module=wishes',
}; };
/** Default callbacks */ /** Default callbacks */

View file

@ -2,7 +2,17 @@ $(function() {
/** /**
* Statistics * Statistics
*/ */
fetch('/src/api/statistics.php?table=all', { const params_statistics = new URLSearchParams(
{
'api_token' : api.token,
'module' : 'statistics',
'page' : 'api',
'table' : 'all',
}
);
fetch('/?' + params_statistics, {
method: 'GET' method: 'GET'
}) })
.then(handleFetchError) .then(handleFetchError)
@ -56,7 +66,15 @@ $(function() {
/** /**
* News * News
*/ */
fetch('/src/api/blog.php', { const params_news = new URLSearchParams(
{
'api_token' : api.token,
'module' : 'blog',
'page' : 'api',
}
);
fetch('/?' + params_news, {
method: 'GET' method: 'GET'
}) })
.then(handleFetchError) .then(handleFetchError)

View file

@ -9,7 +9,7 @@ $(function() {
form.addClass('loading'); form.addClass('loading');
fetch('/src/api/database-test.php', { fetch('/?page=api&module=database-test', {
method : 'POST', method : 'POST',
body : formDatabase body : formDatabase
}) })

View file

@ -28,12 +28,18 @@ $(function () {
return false; return false;
} }
var paramater = new URLSearchParams({ const parameter = new URLSearchParams(
wishlist : wishlist_id, {
priority : $(this).dropdown('get value'), 'api_token' : api.token,
}); 'module' : 'wishlists',
'page' : 'api',
fetch('/src/api/wishlists.php?' + paramater, { 'priority' : $(this).dropdown('get value'),
'wishlist' : wishlist_id,
}
);
fetch('/?' + parameter, {
method : 'GET', method : 'GET',
}) })
.then(handleFetchError) .then(handleFetchError)

View file

@ -47,8 +47,10 @@ $(function() {
action : 'update wish status', action : 'update wish status',
method : 'PUT', method : 'PUT',
data : { data : {
wish_id : card.attr('data-id'), 'api_token' : api.token,
wish_status : wish_status_temporary,
'wish_id' : card.attr('data-id'),
'wish_status' : wish_status_temporary,
}, },
on : 'now', on : 'now',
onSuccess : function(response, element, xhr) { onSuccess : function(response, element, xhr) {
@ -66,8 +68,10 @@ $(function() {
action : 'update wish status', action : 'update wish status',
method : 'PUT', method : 'PUT',
data : { data : {
wish_id : card.attr('data-id'), 'api_token' : api.token,
wish_status : wish_status_unavailable,
'wish_id' : card.attr('data-id'),
'wish_status' : wish_status_unavailable,
}, },
on : 'now', on : 'now',
onSuccess : function(response, element, xhr) { onSuccess : function(response, element, xhr) {
@ -84,10 +88,15 @@ $(function() {
buttonSave.addClass('disabled loading'); buttonSave.addClass('disabled loading');
var formData = new URLSearchParams(); var formData = new URLSearchParams(
formData.append('wishlist', $('[data-wishlist]').attr('data-wishlist')); {
'api_token' : api.token,
fetch('/src/api/wishlists-saved.php', { 'wishlist' : $('[data-wishlist]').attr('data-wishlist'),
}
);
fetch('/?page=api&module=wishlists-saved', {
method : 'POST', method : 'POST',
body : formData body : formData
}) })
@ -110,7 +119,15 @@ $(function() {
}); });
/** Determine if list is saved */ /** Determine if list is saved */
fetch('/src/api/wishlists-saved.php', { const params_ws_saved = new URLSearchParams(
{
'api_token' : api.token,
'module' : 'wishlists-saved',
'page' : 'api',
}
);
fetch('/?' + params_ws_saved, {
method : 'GET', method : 'GET',
}) })
.then(handleFetchError) .then(handleFetchError)
@ -149,14 +166,18 @@ $(function() {
var wishlist_id = $('.wishlist-cards[data-wishlist]').attr('data-wishlist'); var wishlist_id = $('.wishlist-cards[data-wishlist]').attr('data-wishlist');
var wishlist_locale = buttonRequest.attr('data-locale'); var wishlist_locale = buttonRequest.attr('data-locale');
var formData = new URLSearchParams({ var formData = new URLSearchParams(
'wishlist-id' : wishlist_id, {
'locale' : wishlist_locale 'api_token' : api.token,
});
'locale' : wishlist_locale,
'wishlist-id' : wishlist_id,
}
);
buttonRequest.addClass('disabled loading'); buttonRequest.addClass('disabled loading');
fetch('/src/api/wishlists.php', { fetch('/?page=api&module=wishlists', {
method : 'POST', method : 'POST',
body : formData body : formData
}) })

View file

@ -21,6 +21,9 @@ $(function () {
action : 'get wishlists', action : 'get wishlists',
method : 'GET', method : 'GET',
on : 'now', on : 'now',
data : {
'api_token' : api.token,
},
onSuccess : function (response, element, xhr) { onSuccess : function (response, element, xhr) {
wishlists = response.results; wishlists = response.results;
@ -63,7 +66,16 @@ $(function () {
/** Update URL */ /** Update URL */
urlParams.set('id', wishlistValue); urlParams.set('id', wishlistValue);
fetch('/src/api/url.php?url=' + window.btoa(urlParams.toString()), { const params_url = new URLSearchParams(
{
'api_token' : api.token,
'module' : 'url',
'page' : 'api',
'url' : window.btoa(urlParams.toString()),
}
);
fetch('/?' + params_url, {
method: 'GET' method: 'GET'
}) })
.then(handleFetchError) .then(handleFetchError)
@ -146,7 +158,18 @@ $(function () {
card.addClass('loading'); card.addClass('loading');
card.attr('data-cache', 'false'); card.attr('data-cache', 'false');
fetch('/src/api/wishes.php?wish_id=' + card.attr('data-id') + '&wishlist_user=' + wishlist_user, { const params_cache = new URLSearchParams(
{
'api_token' : api.token,
'module' : 'wishes',
'page' : 'api',
'wish_id' : card.attr('data-id'),
'wishlist_user' : wishlist_user,
}
);
fetch('/?' + params_cache, {
method: 'GET' method: 'GET'
}) })
.then(handleFetchError) .then(handleFetchError)
@ -198,10 +221,11 @@ $(function () {
var formRename = modalRename.find('.form.wishlist-rename'); var formRename = modalRename.find('.form.wishlist-rename');
var formData = new URLSearchParams(new FormData(formRename[0])); var formData = new URLSearchParams(new FormData(formRename[0]));
formData.append('api_token', api.token);
fetch('/src/api/wishlists.php', { fetch('/?page=api&module=wishlists', {
method: 'PUT', method : 'PUT',
body: formData body : formData,
}) })
.then(handleFetchError) .then(handleFetchError)
.then(handleFetchResponse) .then(handleFetchResponse)
@ -280,7 +304,9 @@ $(function () {
action: 'delete wishlist', action: 'delete wishlist',
method: 'DELETE', method: 'DELETE',
data: { data: {
wishlistID: wishlistValue 'api_token' : api.token,
'wishlistID' : wishlistValue
}, },
on: 'now', on: 'now',
onSuccess: function (response, wishlists) { onSuccess: function (response, wishlists) {
@ -323,8 +349,10 @@ $(function () {
action : 'update wish status', action : 'update wish status',
method : 'PUT', method : 'PUT',
data : { data : {
wish_id : card.attr('data-id'), 'api_token' : api.token,
wish_status : wish_status_fulfilled,
'wish_id' : card.attr('data-id'),
'wish_status' : wish_status_fulfilled,
}, },
on : 'now', on : 'now',
onSuccess : function(response, element, xhr) { onSuccess : function(response, element, xhr) {
@ -362,11 +390,17 @@ $(function () {
/** Get Wish */ /** Get Wish */
var wishID = $(this).attr('data-id'); var wishID = $(this).attr('data-id');
var wishFormData = new URLSearchParams({ var wishFormData = new URLSearchParams(
'wish_id' : wishID {
}); 'api_token' : api.token,
'module' : 'wishes',
'page' : 'api',
fetch('/src/api/wishes.php?' + wishFormData, { 'wish_id' : wishID
}
);
fetch('/?' + wishFormData, {
method: 'GET' method: 'GET'
}) })
.then(handleFetchError) .then(handleFetchError)
@ -447,7 +481,9 @@ $(function () {
action : 'delete wish', action : 'delete wish',
method : 'DELETE', method : 'DELETE',
data : { data : {
wish_id: card.attr('data-id'), 'api_token' : api.token,
'wish_id': card.attr('data-id'),
}, },
on : 'now', on : 'now',
onSuccess : function () { onSuccess : function () {
@ -545,14 +581,15 @@ $(function () {
.modal({ .modal({
autoShow: true, autoShow: true,
onApprove: function (buttonCreate) { onApprove: function (buttonCreate) {
const formData = new URLSearchParams(new FormData(formWishlistCreate[0]));
formWishlistCreate.addClass('loading'); formWishlistCreate.addClass('loading');
buttonCreate.addClass('loading'); buttonCreate.addClass('loading');
fetch('/src/api/wishlists.php', { var formData = new URLSearchParams(new FormData(formWishlistCreate[0]));
method: 'POST', formData.append('api_token', api.token);
body: formData
fetch('/?page=api&module=wishlists', {
method : 'POST',
body : formData
}) })
.then(handleFetchError) .then(handleFetchError)
.then(handleFetchResponse) .then(handleFetchResponse)
@ -603,7 +640,17 @@ $(function () {
buttonAddOrSave.addClass('disabled'); buttonAddOrSave.addClass('disabled');
if (wishURLCurrent) { if (wishURLCurrent) {
fetch('/src/api/wishes.php?wish_url=' + wishURLCurrent, { const params_url = new URLSearchParams(
{
'api_token' : api.token,
'module' : 'wishes',
'page' : 'api',
'wish_url' : wishURLCurrent
}
);
fetch('/?=' + params_url, {
method: 'GET' method: 'GET'
}) })
.then(handleFetchError) .then(handleFetchError)
@ -631,14 +678,18 @@ $(function () {
onApprove : function (buttonUpdate) { onApprove : function (buttonUpdate) {
inputURL.val(modalValidate.find('input.proposed').val()); inputURL.val(modalValidate.find('input.proposed').val());
var formData = new URLSearchParams({
'wish_url_current' : modalValidate.find('input.current').val(),
'wish_url_proposed' : modalValidate.find('input.proposed').val()
});
buttonUpdate.addClass('loading'); buttonUpdate.addClass('loading');
fetch('/src/api/wishes.php', { const formData = new URLSearchParams(
{
'api_token' : api.token,
'wish_url_current' : modalValidate.find('input.current').val(),
'wish_url_proposed' : modalValidate.find('input.proposed').val(),
}
);
fetch('/?page=api&module=wishes', {
method : 'PUT', method : 'PUT',
body : formData body : formData
}) })
@ -661,8 +712,9 @@ $(function () {
/** Save form edit fields */ /** Save form edit fields */
/** This code block is a duplicate, please refactor */ /** This code block is a duplicate, please refactor */
var formData = new URLSearchParams(new FormData(formAddOrEdit[0])); var formData = new URLSearchParams(new FormData(formAddOrEdit[0]));
formData.append('api_token', api.token);
fetch('/src/api/wishes.php', { fetch('/?page=api&module=wishes', {
method : 'POST', method : 'POST',
body : formData body : formData
}) })
@ -692,8 +744,9 @@ $(function () {
/** Save form edit fields */ /** Save form edit fields */
/** This code block is a duplicate, please refactor */ /** This code block is a duplicate, please refactor */
var formData = new URLSearchParams(new FormData(formAddOrEdit[0])); var formData = new URLSearchParams(new FormData(formAddOrEdit[0]));
formData.append('api_token', api.token);
fetch('/src/api/wishes.php', { fetch('/?page=api&module=wishes', {
method : 'POST', method : 'POST',
body : formData body : formData
}) })

96
src/classes/api.php Normal file
View file

@ -0,0 +1,96 @@
<?php
/**
* API
*
* @author Jay Trees <github.jay@grandel.anonaddy.me>
*/
namespace wishthis;
class API
{
/**
* Non-Static
*/
private string $module;
private string $module_path;
private array $input;
public string $token;
public function __construct()
{
global $options;
$this->input = $this->getRequestVariables();
$this->module = $this->input['module'] ?? '';
$this->module_path = ROOT . '/src/api/' . $this->module . '.php';
$this->token = $options->getOption('api_token');
/** For installer */
if (empty($this->token)) {
$this->token = sha1(ROOT);
}
}
public function do()
{
if (file_exists($this->module_path)) {
if (!empty(trim($this->input['api_token']))) {
if ($this->input['api_token'] === $this->token) {
ob_start();
$response = array();
require $this->module_path;
$response['warning'] = ob_get_clean();
header('Content-type: application/json; charset=utf-8');
echo json_encode($response);
} else {
http_response_code(403);
?>
<h1>Forbidden</h1>
<p>The specified API token "<?= $this->input['api_token'] ?>" is invalid.</p>
<?php
}
} else {
http_response_code(403);
?>
<h1>Forbidden</h1>
<p>Please specify an API token.</p>
<?php
}
} else {
http_response_code(404);
?>
<h1>Not found</h1>
<p>The API module "<?= $this->module ?>" was not found.</p>
<?php
}
die();
}
private function getRequestVariables(): array
{
$request_variables = $_GET;
switch ($_SERVER['REQUEST_METHOD']) {
case 'POST':
$request_variables = array_merge($request_variables, $_POST);
break;
default:
parse_str(file_get_contents("php://input"), $_INPUT);
$request_variables = array_merge($request_variables, $_INPUT);
break;
}
return $request_variables;
}
}

View file

@ -22,6 +22,7 @@ class Page
/** /**
* Static * Static
*/ */
public const PAGE_API = '/?page=api';
public const PAGE_BLOG = '/?page=blog'; public const PAGE_BLOG = '/?page=blog';
public const PAGE_CHANGELOG = '/?page=changelog'; public const PAGE_CHANGELOG = '/?page=changelog';
public const PAGE_HOME = '/?page=home'; public const PAGE_HOME = '/?page=home';
@ -128,14 +129,26 @@ class Page
*/ */
public function __construct(string $filepath, public string $title = 'wishthis', public int $power = 0) public function __construct(string $filepath, public string $title = 'wishthis', public int $power = 0)
{ {
global $options;
$this->name = pathinfo($filepath, PATHINFO_FILENAME); $this->name = pathinfo($filepath, PATHINFO_FILENAME);
$this->description = __('wishthis is a simple, intuitive and modern wishlist platform to create, manage and view your wishes for any kind of occasion.'); $this->description = __('wishthis is a simple, intuitive and modern wishlist platform to create, manage and view your wishes for any kind of occasion.');
$this->link_preview = 'https://' . $_SERVER['HTTP_HOST'] . '/src/assets/img/link-previews/default.png'; $this->link_preview = 'https://' . $_SERVER['HTTP_HOST'] . '/src/assets/img/link-previews/default.png';
/**
* Install
*/
if (!isset($options) || !$options || !$options->getOption('isInstalled')) {
global $page;
if ('api' !== $page) {
redirect(Page::PAGE_INSTALL);
}
}
/** /**
* Session * Session
*/ */
global $options;
$user = isset($_SESSION['user']->id) ? $_SESSION['user'] : new User(); $user = isset($_SESSION['user']->id) ? $_SESSION['user'] : new User();
$ignorePower = array( $ignorePower = array(
@ -336,10 +349,14 @@ class Page
/** /**
* Scripts * Scripts
*/ */
global $options;
?> ?>
<script type="text/javascript"> <script type="text/javascript">
var locale = '<?= str_replace('_', '-', $this->language) ?>'; var locale = '<?= str_replace('_', '-', $this->language) ?>';
var $_GET = JSON.parse('<?= isset($_GET) ? json_encode($_GET) : json_encode(array()) ?>'); var $_GET = JSON.parse('<?= isset($_GET) ? json_encode($_GET) : json_encode(array()) ?>');
var api = {
'token' : "<?= $options->getOption('api_token'); ?>",
};
var wish_status_temporary = '<?= Wish::STATUS_TEMPORARY ?>'; var wish_status_temporary = '<?= Wish::STATUS_TEMPORARY ?>';
var wish_status_unavailable = '<?= Wish::STATUS_UNAVAILABLE ?>'; var wish_status_unavailable = '<?= Wish::STATUS_UNAVAILABLE ?>';
var wish_status_fulfilled = '<?= Wish::STATUS_FULFILLED ?>'; var wish_status_fulfilled = '<?= Wish::STATUS_FULFILLED ?>';

13
src/pages/api.php Normal file
View file

@ -0,0 +1,13 @@
<?php
/**
* API
*
* @author Jay Trees <github.jay@grandel.anonaddy.me>
*/
namespace wishthis;
$page = new Page(__FILE__, __('API'));
$api = new API();
$api->do();

View file

@ -30,6 +30,29 @@ $page->navigation();
<div class="twelve wide stretched column"> <div class="twelve wide stretched column">
<div class="ui tab" data-tab="0-7-1"> <div class="ui tab" data-tab="0-7-1">
<div class="ui tab" data-tab="1-7-1">
<div class="ui segments">
<div class="ui segment">
<h2 class="ui header"><?= __('1.7.1') ?></h2>
</div>
<div class="ui segment">
<h3 class="ui header"><?= __('Improved') ?></h3>
<ul>
<li>
<?php
/** TRANSLATORS: Changelog: Improved */
echo __('Completely reworked the API');
?>
</li>
</ul>
</div>
</div>
</div>
<div class="ui tab" data-tab="0-7-1">
<div class="ui segments"> <div class="ui segments">
<div class="ui segment"> <div class="ui segment">

View file

@ -43,6 +43,7 @@ switch ($step) {
<form class="ui form" action="<?= Page::PAGE_INSTALL ?>" method="POST"> <form class="ui form" action="<?= Page::PAGE_INSTALL ?>" method="POST">
<input type="hidden" name="step" value="<?= $step + 1; ?>" /> <input type="hidden" name="step" value="<?= $step + 1; ?>" />
<input type="hidden" name="api_token" value="<?= sha1(ROOT) ?>">
<div class="ui error message"></div> <div class="ui error message"></div>
@ -246,11 +247,13 @@ switch ($step) {
); );
$database->query( $database->query(
'INSERT INTO `options` 'INSERT INTO
(`key`, `value`) `options` (`key`, `value`)
VALUES VALUES
("isInstalled", true), ("isInstalled", true),
("version", "' . VERSION . '");' ("version", "' . VERSION . '"),
("api_token", UUID())
;'
); );
/** /**

7
src/update/1-7-1.sql Normal file
View file

@ -0,0 +1,7 @@
/**
* Options
*/
INSERT INTO
`options` (`key`, `value`)
VALUES
('api_token', UUID());