Add 1.x migration script

This commit is contained in:
Bartek Fabiszewski 2020-05-20 17:12:07 +02:00
parent 93b3848f81
commit 88f9178fc4
15 changed files with 1015 additions and 13 deletions

View File

@ -0,0 +1,40 @@
<?php
/* μlogger
*
* Copyright(C) 2020 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
$mapapi = "testApi";
$ol_layers["TestLayer"] = "https://test_tile.png";
$init_latitude = 12.34;
$init_longitude = 12.34;
$gkey = "testGoogleKey";
$dbdsn = "";
$dbuser = "";
$dbpass = "";
$dbprefix = "";
$require_authentication = 1;
$public_tracks = 0;
$admin_user = "admin";
$pass_lenmin = 12;
$pass_strength = 2;
$interval = 1234;
$lang = "pl";
$units = "imperial";
$strokeWeight = 22;
$strokeColor = "#abcdef";
$strokeOpacity = 1;
?>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" ?>
<dataset>
<users id="1" login="admin" password="$2y$10$7OvZrKgonVZM9lkzrTbiou.CVhO3HjPk5y0W9L68fVwPs/osBRIMq" />
<tracks id="1" user_id="1" name="test track" comment="test comment" />
<positions id="1" time="2020-05-18" user_id="1" track_id="1" latitude="1" longitude="1" />
</dataset>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" ?>
<dataset>
<users id="1" login="admin" password="$2y$10$7OvZrKgonVZM9lkzrTbiou.CVhO3HjPk5y0W9L68fVwPs/osBRIMq" admin="0" />
<tracks />
<positions />
<config name="color_extra" value="s:7:&quot;#000000&quot;;" />
<config name="color_hilite" value="s:7:&quot;#000000&quot;;" />
<config name="color_normal" value="s:7:&quot;#000000&quot;;" />
<config name="color_start" value="s:7:&quot;#000000&quot;;" />
<config name="color_stop" value="s:7:&quot;#000000&quot;;" />
<config name="google_key" value="s:0:&quot;&quot;;" />
<config name="interval_seconds" value="i:0;" />
<config name="lang" value="s:2:&quot;en&quot;;" />
<config name="latitude" value="d:0;" />
<config name="longitude" value="d:0;" />
<config name="map_api" value="s:10:&quot;openlayers&quot;;" />
<config name="pass_lenmin" value="i:0;" />
<config name="pass_strength" value="i:0;" />
<config name="public_tracks" value="b:0;" />
<config name="require_auth" value="b:0;" />
<config name="stroke_color" value="s:7:&quot;#000000&quot;;" />
<config name="stroke_opacity" value="d:0;" />
<config name="stroke_weight" value="i:0;" />
<config name="units" value="s:6:&quot;metric&quot;;" />
<ol_layers />
</dataset>

View File

@ -0,0 +1,87 @@
/*
* μlogger
*
* Copyright(C) 2020 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
--
-- Database schema for μlogger 0.6
--
-- --------------------------------------------------------
--
-- Drop tables if exist
--
DROP TABLE IF EXISTS `positions`;
DROP TABLE IF EXISTS `tracks`;
DROP TABLE IF EXISTS `users`;
DROP TABLE IF EXISTS `config`;
DROP TABLE IF EXISTS `ol_layers`;
-- --------------------------------------------------------
--
-- Table structure for table `users`
--
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`login` varchar(15) CHARACTER SET latin1 NOT NULL UNIQUE,
`password` varchar(255) CHARACTER SET latin1 NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Table structure for table `tracks`
--
CREATE TABLE `tracks` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`comment` varchar(1024) DEFAULT NULL,
INDEX `idx_user_id` (`user_id`),
FOREIGN KEY(`user_id`) REFERENCES `users`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- --------------------------------------------------------
--
-- Table structure for table `positions`
--
CREATE TABLE `positions` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`user_id` int(11) NOT NULL,
`track_id` int(11) NOT NULL,
`latitude` double NOT NULL,
`longitude` double NOT NULL,
`altitude` double DEFAULT NULL,
`speed` double DEFAULT NULL,
`bearing` double DEFAULT NULL,
`accuracy` int(11) DEFAULT NULL,
`provider` varchar(100) DEFAULT NULL,
`comment` varchar(255) DEFAULT NULL,
`image_id` int(11) DEFAULT NULL,
INDEX `idx_ptrack_id` (`track_id`),
INDEX `index_puser_id` (`user_id`),
FOREIGN KEY(`user_id`) REFERENCES `users`(`id`),
FOREIGN KEY(`track_id`) REFERENCES `tracks`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

View File

@ -0,0 +1,89 @@
/*
* μlogger
*
* Copyright(C) 2020 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
--
-- Database schema for μlogger 0.6
--
-- --------------------------------------------------------
--
-- Drop tables if exist
--
DROP TABLE IF EXISTS positions;
DROP TABLE IF EXISTS tracks;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS config;
DROP TABLE IF EXISTS ol_layers;
-- --------------------------------------------------------
--
-- Table structure for table `users`
--
CREATE TABLE users (
id serial PRIMARY KEY,
login varchar(15) NOT NULL UNIQUE,
password varchar(255) NOT NULL DEFAULT ''
);
-- --------------------------------------------------------
--
-- Table structure for table `tracks`
--
CREATE TABLE tracks (
id serial PRIMARY KEY,
user_id int NOT NULL,
name varchar(255) DEFAULT NULL,
comment varchar(1024) DEFAULT NULL,
FOREIGN KEY(user_id) REFERENCES users(id)
);
CREATE INDEX idx_user_id ON tracks(user_id);
-- --------------------------------------------------------
--
-- Table structure for table `positions`
--
CREATE TABLE positions (
id serial PRIMARY KEY,
time timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
user_id int NOT NULL,
track_id int NOT NULL,
latitude double precision NOT NULL,
longitude double precision NOT NULL,
altitude double precision DEFAULT NULL,
speed double precision DEFAULT NULL,
bearing double precision DEFAULT NULL,
accuracy int DEFAULT NULL,
provider varchar(100) DEFAULT NULL,
comment varchar(255) DEFAULT NULL,
image_id int DEFAULT NULL,
FOREIGN KEY(user_id) REFERENCES users(id),
FOREIGN KEY(track_id) REFERENCES tracks(id)
);
CREATE INDEX idx_ptrack_id ON positions(track_id);
CREATE INDEX idx_puser_id ON positions(user_id);

View File

@ -0,0 +1,87 @@
/*
* μlogger
*
* Copyright(C) 2020 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
--
-- Database schema for μlogger 0.6
--
-- --------------------------------------------------------
--
-- Drop tables if exist
--
DROP TABLE IF EXISTS `positions`;
DROP TABLE IF EXISTS `tracks`;
DROP TABLE IF EXISTS `users`;
DROP TABLE IF EXISTS `config`;
DROP TABLE IF EXISTS `ol_layers`;
-- --------------------------------------------------------
--
-- Table structure for table `users`
--
CREATE TABLE `users` (
`id` integer PRIMARY KEY AUTOINCREMENT,
`login` varchar(15) NOT NULL UNIQUE,
`password` varchar(255) NOT NULL DEFAULT ''
);
-- --------------------------------------------------------
--
-- Table structure for table `tracks`
--
CREATE TABLE `tracks` (
`id` integer PRIMARY KEY AUTOINCREMENT,
`user_id` integer NOT NULL,
`name` varchar(255) DEFAULT NULL,
`comment` varchar(1024) DEFAULT NULL,
FOREIGN KEY(`user_id`) REFERENCES `users`(`id`)
);
CREATE INDEX `idx_user_id` ON `tracks`(`user_id`);
-- --------------------------------------------------------
--
-- Table structure for table `positions`
--
CREATE TABLE `positions` (
`id` integer PRIMARY KEY AUTOINCREMENT,
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`user_id` integer NOT NULL,
`track_id` integer NOT NULL,
`latitude` double NOT NULL,
`longitude` double NOT NULL,
`altitude` double DEFAULT NULL,
`speed` double DEFAULT NULL,
`bearing` double DEFAULT NULL,
`accuracy` integer DEFAULT NULL,
`provider` varchar(100) DEFAULT NULL,
`comment` varchar(255) DEFAULT NULL,
`image_id` integer DEFAULT NULL,
FOREIGN KEY(`user_id`) REFERENCES `users`(`id`),
FOREIGN KEY(`track_id`) REFERENCES `tracks`(`id`)
);
CREATE INDEX `idx_ptrack_id` ON `positions`(`track_id`);
CREATE INDEX `idx_puser_id` ON `positions`(`user_id`);

View File

@ -97,10 +97,10 @@ abstract class BaseDatabaseTestCase extends PHPUnit_Extensions_Database_TestCase
protected function resetAutoincrement($users = 1, $tracks = 1, $positions = 1, $layers = 1) {
if (self::$driver === "pgsql") {
self::$pdo->exec("ALTER SEQUENCE users_id_seq RESTART WITH $users");
self::$pdo->exec("ALTER SEQUENCE tracks_id_seq RESTART WITH $tracks");
self::$pdo->exec("ALTER SEQUENCE positions_id_seq RESTART WITH $positions");
self::$pdo->exec("ALTER SEQUENCE ol_layers_id_seq RESTART WITH $layers");
self::$pdo->exec("ALTER SEQUENCE IF EXISTS users_id_seq RESTART WITH $users");
self::$pdo->exec("ALTER SEQUENCE IF EXISTS tracks_id_seq RESTART WITH $tracks");
self::$pdo->exec("ALTER SEQUENCE IF EXISTS positions_id_seq RESTART WITH $positions");
self::$pdo->exec("ALTER SEQUENCE IF EXISTS ol_layers_id_seq RESTART WITH $layers");
} else if (self::$driver === "sqlite") {
$retry = 1;
do {
@ -121,6 +121,17 @@ abstract class BaseDatabaseTestCase extends PHPUnit_Extensions_Database_TestCase
}
}
/**
* Reset connection
* Fixes sqlite error when db schema changes in another connection.
*/
protected function resetConnection() {
$this->closeConnection($this->conn);
$this->conn = null;
self::tearDownAfterClass();
self::setUpBeforeClass();
}
/**
* Insert to database from array
*

View File

@ -1,5 +1,4 @@
<?php
use PHPUnit\Framework\TestCase;
require_once("BaseDatabaseTestCase.php");
require_once(__DIR__ . "/../../helpers/db.php");
@ -11,6 +10,9 @@ class UloggerDatabaseTestCase extends BaseDatabaseTestCase {
*/
static private $udb = null;
/**
* @throws ReflectionException
*/
public static function setUpBeforeClass() {
parent::setUpBeforeClass();

View File

@ -32,6 +32,17 @@ class ConfigTest extends UloggerDatabaseTestCase {
$this->initConfigValues();
}
/**
* @throws ReflectionException
*/
protected function tearDown() {
parent::tearDown();
$configClass = new ReflectionClass("uConfig");
$configInstance = $configClass->getProperty('instance');
$configInstance->setAccessible(true);
$configInstance->setValue(null);
}
protected function getDataSet() {
$this->initConfigValues();
$this->resetAutoincrement();

View File

@ -0,0 +1,155 @@
<?php
/**
* μlogger
*
* Copyright(C) 2020 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
define("SKIP_RUN", true);
require_once(dirname(__DIR__) . "/../scripts/migrate_to_1_x.php");
require_once(dirname(__DIR__) . "/lib/UloggerDatabaseTestCase.php");
class MigrateTest extends UloggerDatabaseTestCase {
protected function tearDown() {
if ($this->getName() === "testUpdateSchemas") {
self::runSqlScript(dirname(__DIR__) . "/../scripts/ulogger." . $this->getDbDriverName());
}
parent::tearDown();
}
public function testUpdateSchemas() {
self::runSqlScript(dirname(__DIR__) . "/fixtures/ulogger_0_6." . $this->getDbDriverName());
$this->loadDataSet("fixture_0_6.xml");
$this->assertEquals(1, $this->getConnection()->getRowCount("users"), "Wrong row count");
$this->assertNotContains("admin", $this->getConnection()->getMetaData()->getTableColumns("users"));
$this->assertContains("image_id", $this->getConnection()->getMetaData()->getTableColumns("positions"));
$this->assertNotContains("ol_layers", $this->getConnection()->getMetaData()->getTableNames());
$this->assertNotContains("config", $this->getConnection()->getMetaData()->getTableNames());
$this->setOutputCallback(static function() {});
$ret = updateSchemas();
$this->resetConnection();
$this->assertEquals(true, $ret, "Function updateSchemas() failed");
$this->assertEquals(1, $this->getConnection()->getRowCount("users"), "Wrong row count");
$this->assertEquals(1, $this->getConnection()->getRowCount("tracks"), "Wrong row count");
$this->assertEquals(1, $this->getConnection()->getRowCount("positions"), "Wrong row count");
$this->assertContains("admin", $this->getConnection()->getMetaData()->getTableColumns("users"), "Missing table column");
$this->assertContains("image", $this->getConnection()->getMetaData()->getTableColumns("positions"), "Missing table column");
$this->assertContains("ol_layers", $this->getConnection()->getMetaData()->getTableNames(), "Missing table");
$this->assertContains("config", $this->getConnection()->getMetaData()->getTableNames(), "Missing table");
}
public function testUpdateConfig() {
$this->loadDataSet("fixture_non_admin.xml");
$this->setOutputCallback(static function() {});
$ret = updateConfig(dirname(__DIR__) . "/fixtures/config_0_6.php");
$this->assertEquals(true, $ret, "Function updateConfig() failed");
// admin user imported from config file
$expected = [ "admin" => 1 ];
$actual = $this->getConnection()->createQueryTable(
"admin",
"SELECT admin FROM users WHERE login = 'admin'"
);
$this->assertTableContains($expected, $actual, "Wrong actual table data");
// settings imported from config file
$expected = [ "config" => [
["name" => "color_extra", "value" => "s:7:\"#cccccc\";"], // default
["name" => "color_hilite", "value" => "s:7:\"#feff6a\";"], // default
["name" => "color_normal", "value" => "s:7:\"#ffffff\";"], // default
["name" => "color_start", "value" => "s:7:\"#55b500\";"], // default
["name" => "color_stop", "value" => "s:7:\"#ff6a00\";"], // default
["name" => "google_key", "value" => "s:13:\"testGoogleKey\";"],
["name" => "interval_seconds", "value" => "i:1234;"],
["name" => "lang", "value" => "s:2:\"pl\";"],
["name" => "latitude", "value" => "d:12.34;"],
["name" => "longitude", "value" => "d:12.34;"],
["name" => "map_api", "value" => "s:7:\"testApi\";"],
["name" => "pass_lenmin", "value" => "i:12;"],
["name" => "pass_strength", "value" => "i:2;"],
["name" => "public_tracks", "value" => "b:0;"],
["name" => "require_auth", "value" => "b:1;"],
["name" => "stroke_color", "value" => "s:7:\"#abcdef\";"],
["name" => "stroke_opacity", "value" => "i:1;"],
["name" => "stroke_weight", "value" => "i:22;"],
["name" => "units", "value" => "s:8:\"imperial\";"]
]];
$actual = $this->getConnection()->createQueryTable(
"config",
"SELECT name, " . uDb::getInstance()->from_lob("value") . " FROM config ORDER BY name"
);
$expected = $this->createArrayDataSet($expected)->getTable("config");
self::assertTablesEqual($expected, $actual);
// layers imported from config file
$this->assertEquals(1, $this->getConnection()->getRowCount("ol_layers"), "Wrong row count");
$expected = [ "id" => 1, "name" => "TestLayer", "url" => "https://test_tile.png", "priority" => 0 ];
$actual = $this->getConnection()->createQueryTable(
"ol_layers",
"SELECT * FROM ol_layers"
);
$this->assertTableContains($expected, $actual, "Wrong actual table data");
}
public function testWaitForUser() {
$this->setOutputCallback(static function() {});
$yes = tmpfile();
fwrite($yes, "yes");
$ret = waitForUser(stream_get_meta_data($yes)['uri']);
fclose($yes);
$this->assertEquals(true, $ret, "Wrong return status");
$no = tmpfile();
fwrite($no, "no");
$ret = waitForUser(stream_get_meta_data($no)['uri']);
fclose($no);
$this->assertEquals(false, $ret, "Wrong return status");
}
/**
* Run SQL commands from file.
* Basic subset only. Multiple commands must not be on the same line.
* @param string $path Script path
* @throws PDOException
*/
private static function runSqlScript($path) {
$script = file_get_contents($path);
$count = preg_match_all('/^(?:(?:DROP|CREATE) (?:TABLE|INDEX)|INSERT|PRAGMA|SET) .*?;\s*$/smi', $script, $queries);
if ($count) {
try {
uDb::getInstance()->beginTransaction();
foreach ($queries[0] as $query) {
uDb::getInstance()->exec($query);
}
uDb::getInstance()->commit();
} catch (PDOException $e) {
uDb::getInstance()->rollBack();
throw $e;
}
}
}
private function getDbDriverName() {
return uDb::getInstance()->getAttribute(PDO::ATTR_DRIVER_NAME);
}
private function loadDataSet($name) {
$this->resetAutoincrement();
$dataSet = $this->createFlatXMLDataSet(dirname(__DIR__) . '/fixtures/' . $name);
$this->getDatabaseTester()->setDataSet($dataSet);
$this->getDatabaseTester()->onSetUp();
}
}
?>

View File

@ -43,13 +43,20 @@ Together with a dedicated [μlogger mobile client](https://github.com/bfabiszews
- Folders `.docker/` and `.tests/` as well as composer files are needed only for development. May be safely removed.
## Upgrade to version 1.x
- TODO: convert following notes to migration script
- Database changes:
- `ALTER TABLE positions CHANGE image_id image VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL`
- `ALTER TABLE users ADD admin BOOLEAN NOT NULL DEFAULT FALSE AFTER password`
- new tables for config values: `config` and `ol_layers`, see SQL files in scripts folder, eg. [mysql](https://github.com/bfabiszewski/ulogger-server/blob/master/scripts/ulogger.mysql)
- modify admin user entry in `users` table: set `admin` to `true`
- Config file changes: only database setup is defined in config file, see [config.default.php](https://github.com/bfabiszewski/ulogger-server/blob/master/config.default.php) for valid values
- Incompatible changes include database and config file changes
- Upgrading manually:
- for database changes, see MySQL example script in `scripts/migrate_to_1_x.mysql`
- set `admin` column in `users` table to true for admin users
- edit configuration from application settings dialog
- Upgrading with migration script from version 0.6:
- create database backup
- replace all project files with new ones, but keep old local config file: `config.php`
- change directory to application root folder and run migration script from the console: `php scripts/migrate_to_1_x.php`
- the script will update database schema and save old config variables to database
- Additional tasks after manual or script update:
- edit your `config.php` file and remove all variables except database settings, see [config.default.php](https://github.com/bfabiszewski/ulogger-server/blob/master/config.default.php) for valid values
- make sure `uploads` folder (for images uploaded from client app) is writable by PHP
- clear browser cache and restart web page
## Docker
- Run `docker run --name ulogger -p 8080:80 -d bfabiszewski/ulogger` and access `http://localhost:8080` in your browser. Log in with `admin`:`admin` credentials and change default password.

View File

@ -184,6 +184,24 @@ require_once(ROOT_DIR . "/helpers/utils.php");
}
}
/**
* Returns construct for getting LOB as string
* @param string $column Column name
* @return string
*/
public function from_lob($column) {
switch (self::$driver) {
default:
case "mysql":
case "sqlite":
return $column;
break;
case "pgsql":
return "encode($column, 'escape') AS $column";
break;
}
}
/**
* Returns function name for getting date-time column value as 'YYYY-MM-DD hh:mm:ss'
* @param string $column

View File

@ -0,0 +1,87 @@
/*
* μlogger
*
* Copyright(C) 2020 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
--
-- Table structure for table `config`
--
CREATE TABLE `config` (
`name` varchar(20) PRIMARY KEY,
`value` tinyblob NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Data for table `config`
--
INSERT INTO `config` (`name`, `value`) VALUES
('color_extra', 's:7:"#cccccc";'),
('color_hilite', 's:7:"#feff6a";'),
('color_normal', 's:7:"#ffffff";'),
('color_start', 's:7:"#55b500";'),
('color_stop', 's:7:"#ff6a00";'),
('google_key', 's:0:"";'),
('interval_seconds', 'i:10;'),
('lang', 's:2:"en";'),
('latitude', 'd:52.229999999999997;'),
('longitude', 'd:21.010000000000002;'),
('map_api', 's:10:"openlayers";'),
('pass_lenmin', 'i:10;'),
('pass_strength', 'i:2;'),
('public_tracks', 'b:1;'),
('require_auth', 'b:1;'),
('stroke_color', 's:7:"#ff0000";'),
('stroke_opacity', 'd:1;'),
('stroke_weight', 'i:2;'),
('units', 's:6:"metric";');
--
-- Table structure for table `ol_layers`
--
CREATE TABLE `ol_layers` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` varchar(50) NOT NULL,
`url` varchar(255) NOT NULL,
`priority` int(11) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Data for table `ol_layers`
--
INSERT INTO `ol_layers` (`id`, `name`, `url`, `priority`) VALUES
(1, 'OpenCycleMap', 'https://{a-c}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', 0),
(2, 'OpenTopoMap', 'https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png', 0),
(3, 'OpenSeaMap', 'https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png', 0),
(4, 'ESRI', 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', 0),
(5, 'UMP', 'http://{1-3}.tiles.ump.waw.pl/ump_tiles/{z}/{x}/{y}.png', 0),
(6, 'Osmapa.pl', 'http://{a-c}.tile.openstreetmap.pl/osmapa.pl/{z}/{x}/{y}.png', 0);
--
-- Add admin column to `users` table
--
ALTER TABLE `users` ADD `admin` BOOLEAN NOT NULL DEFAULT FALSE AFTER `password`;
--
-- Modify image column in `positions` table
--
ALTER TABLE `positions` CHANGE `image_id` `image` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL;

376
scripts/migrate_to_1_x.php Normal file
View File

@ -0,0 +1,376 @@
<?php
/**
* μlogger
*
* Copyright(C) 2020 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/*
* CLI script for database migration from μlogger version 0.6 to version 1.x.
* Database user defined in config file must have privileges to create and modify tables.
*
* Backup your database before running this script.
*/
if (PHP_SAPI !== "cli") {
die("This script must be called from CLI console");
}
if (!defined("ROOT_DIR")) { define("ROOT_DIR", dirname(__DIR__)); }
include_once(ROOT_DIR . "/helpers/config.php");
include_once(ROOT_DIR . "/helpers/db.php");
if (!defined("SKIP_RUN")) {
verifyVersion() || exit(1);
waitForUser() || exit(0);
updateSchemas() || exit(1);
updateConfig() || exit(1);
exit(0);
}
/**
* Check μlogger version is valid for migration
* @return bool True if valid version
*/
function verifyVersion() {
if (!class_exists("uConfig") || !class_exists("uDb") ||
!method_exists("uConfig", "getOfflineInstance") ||
strpos(uConfig::getOfflineInstance()->version, '1.') !== 0) {
echo "You need μlogger version 1.x to run this script" . PHP_EOL;
return false;
}
return true;
}
/**
* Waits for user confirmation
* @param string $input Optional path to read from (for tests)
* @return bool True if confirmed
*/
function waitForUser($input = "php://stdin") {
echo "This script will update database from version 0.6 to 1.x." . PHP_EOL;
echo "Creating database backup is recommended before proceeding" . PHP_EOL;
echo "Type 'yes' to continue, anything else to abort" . PHP_EOL;
$handle = fopen($input, 'rb');
$input = fgets($handle);
fclose($handle);
if (trim($input) !== "yes") {
echo "Cancelled by user" . PHP_EOL;
return false;
}
echo PHP_EOL;
echo "Starting migration..." . PHP_EOL;
return true;
}
/**
* Updates database schemas
* @return bool True on success
*/
function updateSchemas() {
echo "Migrating database schemas..." . PHP_EOL;
try {
$queries = getQueries();
uDb::getInstance()->beginTransaction();
foreach ($queries as $query) {
uDb::getInstance()->exec($query);
}
uDb::getInstance()->commit();
} catch (PDOException $e) {
echo "Database query failed: {$e->getMessage()}" . PHP_EOL;
echo "Reverting changes..." . PHP_EOL;
uDb::getInstance()->rollBack();
return false;
}
echo PHP_EOL;
echo "Database schemas updated successfully" . PHP_EOL;
return true;
}
/**
* Update database settings based on current config file
* @param string $path Optional path of config (for tests)
* @return bool True on success
*/
/** @noinspection IssetArgumentExistenceInspection, PhpIncludeInspection */
function updateConfig($path = ROOT_DIR . "/config.php") {
echo "Migrating config to database..." . PHP_EOL;
require_once($path);
if (isset($admin_user) && !empty($admin_user)) {
try {
echo "Setting user $admin_user as admin" . PHP_EOL;
$query = "UPDATE " . uDb::getInstance()->table('users') . " SET admin = TRUE WHERE login = ?";
$stmt = uDb::getInstance()->prepare($query);
$stmt->execute([ $admin_user ]);
if ($stmt->rowCount() === 0) {
echo "User $admin_user not found in database table" . PHP_EOL;
echo "Set your admin user manually in users table" . PHP_EOL;
}
} catch (PDOException $e) {
echo "Setting admin user failed: " . $e->getMessage() . PHP_EOL;
echo "Set your admin user manually in users table" . PHP_EOL;
}
}
$config = uConfig::getOfflineInstance();
if (isset($mapapi) && !empty($mapapi)) {
$config->mapApi = $mapapi;
}
if (isset($ol_layers) && is_array($ol_layers)) {
$id = 1;
foreach ($ol_layers as $name => $url) {
$config->olLayers[] = new uLayer($id++, $name, $url, 0);
}
}
if (isset($init_latitude) && is_numeric($init_latitude)) {
$config->initLatitude = $init_latitude;
}
if (isset($init_longitude) && is_numeric($init_longitude)) {
$config->initLongitude = $init_longitude;
}
if (isset($gkey) && !empty($gkey)) {
$config->googleKey = $gkey;
}
if (isset($require_authentication) && is_numeric($require_authentication)) {
$config->requireAuthentication = (bool) $require_authentication;
}
if (isset($public_tracks) && is_numeric($public_tracks)) {
$config->publicTracks = (bool) $public_tracks;
}
if (isset($pass_lenmin) && is_numeric($pass_lenmin)) {
$config->passLenMin = (int) $pass_lenmin;
}
if (isset($pass_strength) && is_numeric($pass_strength)) {
$config->passStrength = (int) $pass_strength;
}
if (isset($interval) && is_numeric($interval)) {
$config->interval = (int) $interval;
}
if (isset($lang) && !empty($lang)) {
$config->lang = $lang;
}
if (isset($units) && !empty($units)) {
$config->units = $units;
}
if (isset($strokeWeight) && is_numeric($strokeWeight)) {
$config->strokeWeight = (int) $strokeWeight;
}
if (isset($strokeColor) && !empty($strokeColor)) {
$config->strokeColor = $strokeColor;
}
if (isset($strokeOpacity) && is_numeric($strokeOpacity)) {
$config->strokeOpacity = $strokeOpacity;
}
if ($config->save() !== true) {
echo "Configuration migration failed. Please update your settings manually from web interface" . PHP_EOL;
return false;
}
echo "Configuration successfully migrated to database" . PHP_EOL;
return true;
}
/**
* Get queries array for current db driver
* @return array Queries
*/
function getQueries() {
$dbDriver = uDb::getInstance()->getAttribute(PDO::ATTR_DRIVER_NAME);
$tConfig = uDb::getInstance()->table('config');
$tLayers = uDb::getInstance()->table('ol_layers');
$tUsers = uDb::getInstance()->table('users');
$tTracks = uDb::getInstance()->table('tracks');
$tPositions = uDb::getInstance()->table('positions');
$tPositionsBackup = "{$tPositions}_backup";
$queries = [];
switch ($dbDriver) {
case "mysql":
$queries[] = "CREATE TABLE `$tConfig` (
`name` varchar(20) PRIMARY KEY,
`value` tinyblob NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8";
$queries[] = "INSERT INTO `$tConfig` (`name`, `value`) VALUES
('color_extra', 's:7:\"#cccccc\";'),
('color_hilite', 's:7:\"#feff6a\";'),
('color_normal', 's:7:\"#ffffff\";'),
('color_start', 's:7:\"#55b500\";'),
('color_stop', 's:7:\"#ff6a00\";'),
('google_key', 's:0:\"\";'),
('interval_seconds', 'i:10;'),
('lang', 's:2:\"en\";'),
('latitude', 'd:52.229999999999997;'),
('longitude', 'd:21.010000000000002;'),
('map_api', 's:10:\"openlayers\";'),
('pass_lenmin', 'i:10;'),
('pass_strength', 'i:2;'),
('public_tracks', 'b:1;'),
('require_auth', 'b:1;'),
('stroke_color', 's:7:\"#ff0000\";'),
('stroke_opacity', 'd:1;'),
('stroke_weight', 'i:2;'),
('units', 's:6:\"metric\";')";
$queries[] = "CREATE TABLE `$tLayers` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` varchar(50) NOT NULL,
`url` varchar(255) NOT NULL,
`priority` int(11) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8";
$queries[] = "INSERT INTO `$tLayers` (`id`, `name`, `url`, `priority`) VALUES
(1, 'OpenCycleMap', 'https://{a-c}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', 0),
(2, 'OpenTopoMap', 'https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png', 0),
(3, 'OpenSeaMap', 'https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png', 0),
(4, 'ESRI', 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', 0),
(5, 'UMP', 'http://{1-3}.tiles.ump.waw.pl/ump_tiles/{z}/{x}/{y}.png', 0),
(6, 'Osmapa.pl', 'http://{a-c}.tile.openstreetmap.pl/osmapa.pl/{z}/{x}/{y}.png', 0);
";
$queries[] = "ALTER TABLE `$tUsers` ADD `admin` BOOLEAN NOT NULL DEFAULT FALSE AFTER `password`";
$queries[] = "ALTER TABLE `$tPositions` CHANGE `image_id` `image` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL";
break;
case "pgsql":
$queries[] = "CREATE TABLE $tConfig (
name varchar(20) PRIMARY KEY,
value bytea NOT NULL
)";
$queries[] = "INSERT INTO $tConfig (name, value) VALUES
('color_extra', 's:7:\"#cccccc\";'),
('color_hilite', 's:7:\"#feff6a\";'),
('color_normal', 's:7:\"#ffffff\";'),
('color_start', 's:7:\"#55b500\";'),
('color_stop', 's:7:\"#ff6a00\";'),
('google_key', 's:0:\"\";'),
('interval_seconds', 'i:10;'),
('lang', 's:2:\"en\";'),
('latitude', 'd:52.229999999999997;'),
('longitude', 'd:21.010000000000002;'),
('map_api', 's:10:\"openlayers\";'),
('pass_lenmin', 'i:10;'),
('pass_strength', 'i:2;'),
('public_tracks', 'b:1;'),
('require_auth', 'b:1;'),
('stroke_color', 's:7:\"#ff0000\";'),
('stroke_opacity', 'd:1;'),
('stroke_weight', 'i:2;'),
('units', 's:6:\"metric\";')";
$queries[] = "CREATE TABLE $tLayers (
id serial PRIMARY KEY,
name varchar(50) NOT NULL,
url varchar(255) NOT NULL,
priority int NOT NULL DEFAULT '0'
)";
$queries[] = "INSERT INTO $tLayers (id, name, url, priority) VALUES
(1, 'OpenCycleMap', 'https://{a-c}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', 0),
(2, 'OpenTopoMap', 'https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png', 0),
(3, 'OpenSeaMap', 'https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png', 0),
(4, 'ESRI', 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', 0),
(5, 'UMP', 'http://{1-3}.tiles.ump.waw.pl/ump_tiles/{z}/{x}/{y}.png', 0),
(6, 'Osmapa.pl', 'http://{a-c}.tile.openstreetmap.pl/osmapa.pl/{z}/{x}/{y}.png', 0);
";
$queries[] = "ALTER TABLE $tUsers ADD COLUMN admin boolean NOT NULL DEFAULT FALSE";
$queries[] = "ALTER TABLE $tPositions DROP COLUMN image_id";
$queries[] = "ALTER TABLE $tPositions ADD COLUMN image varchar(100) NULL DEFAULT NULL";
break;
case "sqlite":
$queries[] = "CREATE TABLE `$tConfig` (
`name` varchar(20) PRIMARY KEY,
`value` tinyblob NOT NULL
)";
$queries[] = "INSERT INTO `$tConfig` (`name`, `value`) VALUES
('color_extra', 's:7:\"#cccccc\";'),
('color_hilite', 's:7:\"#feff6a\";'),
('color_normal', 's:7:\"#ffffff\";'),
('color_start', 's:7:\"#55b500\";'),
('color_stop', 's:7:\"#ff6a00\";'),
('google_key', 's:0:\"\";'),
('interval_seconds', 'i:10;'),
('lang', 's:2:\"en\";'),
('latitude', 'd:52.229999999999997;'),
('longitude', 'd:21.010000000000002;'),
('map_api', 's:10:\"openlayers\";'),
('pass_lenmin', 'i:10;'),
('pass_strength', 'i:2;'),
('public_tracks', 'b:1;'),
('require_auth', 'b:1;'),
('stroke_color', 's:7:\"#ff0000\";'),
('stroke_opacity', 'd:1;'),
('stroke_weight', 'i:2;'),
('units', 's:6:\"metric\";')";
$queries[] = "CREATE TABLE `$tLayers` (
`id` integer PRIMARY KEY AUTOINCREMENT,
`name` varchar(50) NOT NULL,
`url` varchar(255) NOT NULL,
`priority` integer NOT NULL DEFAULT '0'
)";
$queries[] = "INSERT INTO `$tLayers` (`id`, `name`, `url`, `priority`) VALUES
(1, 'OpenCycleMap', 'https://{a-c}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', 0),
(2, 'OpenTopoMap', 'https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png', 0),
(3, 'OpenSeaMap', 'https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png', 0),
(4, 'ESRI', 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', 0),
(5, 'UMP', 'http://{1-3}.tiles.ump.waw.pl/ump_tiles/{z}/{x}/{y}.png', 0),
(6, 'Osmapa.pl', 'http://{a-c}.tile.openstreetmap.pl/osmapa.pl/{z}/{x}/{y}.png', 0);
";
$queries[] = "ALTER TABLE `$tUsers` ADD `admin` boolean NOT NULL DEFAULT FALSE";
$queries[] = "PRAGMA foreign_keys=OFF";
$queries[] = "CREATE TABLE `$tPositionsBackup` (
`id` integer PRIMARY KEY AUTOINCREMENT,
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`user_id` integer NOT NULL,
`track_id` integer NOT NULL,
`latitude` double NOT NULL,
`longitude` double NOT NULL,
`altitude` double DEFAULT NULL,
`speed` double DEFAULT NULL,
`bearing` double DEFAULT NULL,
`accuracy` integer DEFAULT NULL,
`provider` varchar(100) DEFAULT NULL,
`comment` varchar(255) DEFAULT NULL,
`image` varchar(100) DEFAULT NULL,
FOREIGN KEY(`user_id`) REFERENCES `$tUsers`(`id`),
FOREIGN KEY(`track_id`) REFERENCES `$tTracks`(`id`)
)";
$queries[] = "INSERT INTO `$tPositionsBackup` SELECT id, time, user_id, track_id, latitude, longitude, altitude, speed, bearing, accuracy, provider, comment, NULL FROM positions";
$queries[] = "DROP TABLE `$tPositions`";
$queries[] = "ALTER TABLE `$tPositionsBackup` RENAME TO `$tPositions`";
$queries[] = "CREATE INDEX `idx_ptrack_id` ON `$tPositions`(`track_id`)";
$queries[] = "CREATE INDEX `idx_puser_id` ON `$tPositions`(`user_id`)";
$queries[] = "PRAGMA foreign_keys=ON";
break;
default:
throw new InvalidArgumentException("Driver not supported");
}
return $queries;
}
?>

View File

@ -29,7 +29,7 @@ if (version_compare(PHP_VERSION, "5.5.0", "<")) {
die("Sorry, ulogger will not work with PHP version lower than 5.5 (you have " . PHP_VERSION . ")");
}
define("ROOT_DIR", dirname(__DIR__));
if (!defined("ROOT_DIR")) { define("ROOT_DIR", dirname(__DIR__)); }
require_once(ROOT_DIR . "/helpers/db.php");
require_once(ROOT_DIR . "/helpers/config.php");