diff --git a/README.md b/README.md index bf1774e..69390aa 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,14 @@ With this PHP web app you can set up your own server for the [Android](https://g 0. Install Apache 2, PHP 7 (with `php-curl`) and MySQL/MariaDB on a Linux server. 1. Download the [latest release](https://github.com/schorschii/customerdb-server/releases) and unpack it into `/srv/www/customerdb`. 2. Set your Apache (virtual host) web root directory to `/srv/www/customerdb/web` by editing the corresponding configuration file inside `/etc/apache2/sites-enabled`. -3. Create a database on your MySQL server and import the schema from `lib/sql/customerdb.sql`. +3. Create a database on your MySQL server and import the schema from `sql/customerdb.sql`. 4. Edit/create `conf.php` from the example file (`conf.php.example`) and enter your MySQL credentials. Please do not use the root user but create a new user which is only allowed to operate on the specific database. 5. Select "Own Server" in the settings of your Customer Database app and enter the full URL to the `web/api.php` script. Example: `http://192.168.2.10/api.php`. 6. Create an account. You can do this in the app (if the API and registration is enabled in `conf.php`) or by using the command line tool on the server (`php console.php createuser `). ## Installation (On A Managed Server) 1. Download the [latest release](https://github.com/schorschii/customerdb-server/releases) and unpack it into your webspace. -2. Create a database on your MySQL server and import the schema from `lib/sql/customerdb.sql` using phpMyAdmin or a similar web-based tool from your hosting provider. +2. Create a database on your MySQL server and import the schema from `sql/customerdb.sql` using phpMyAdmin or a similar web-based tool from your hosting provider. 3. Edit/create `conf.php` from the example file (`conf.php.example`) and enter your MySQL credentials. 4. Select "Own Server" in the settings of your Customer Database app and enter the full URL to the `web/api.php` script. Example: `http://example.com/web/api.php`. 5. Ensure that the API and registration is enabled in `conf.php`. Now create a sync account inside the app. diff --git a/docs/JSON REST API.md b/docs/JSON REST API.md index cb42a1b..e2308a9 100644 --- a/docs/JSON REST API.md +++ b/docs/JSON REST API.md @@ -18,8 +18,9 @@ A valid JSON-RPC request is sent via HTTP with the HTTP header `Content-Type: ap ``` # Methods -## `customers.read` - get all customers from server +## `customers.read` - get customers from server ### Parameters +- `diff_since` (optional) - only return records changed since this date - `username` - username for authentication - `password` - password for authentication - `playstore_token` (optional) - Google Play Store token for server-side subscription payment check (not of interest for self-hosted servers) @@ -31,6 +32,7 @@ A valid JSON-RPC request is sent via HTTP with the HTTP header `Content-Type: ap "id": 1, "method": "customerdb.read", "params": { + "diff_since": "2022-11-01 20:48:00", "username": "test@example.com", "password": "12345678" } @@ -75,7 +77,7 @@ A valid JSON-RPC request is sent via HTTP with the HTTP header `Content-Type: ap } ``` -## `customers.put` - upload/update all customers to server +## `customers.put` - update/create customers on server ### Parameters - `username` - username for authentication - `password` - password for authentication diff --git a/docs/Upgrade.md b/docs/Upgrade.md index 83a31f6..e51a7da 100644 --- a/docs/Upgrade.md +++ b/docs/Upgrade.md @@ -1,13 +1,17 @@ # Upgrade Instructions +## v1.3 +- upgrade database schema as defined in `lib/customerdb.sql` + - add column 'last_modified_on_server' to Customer, Voucher, Calendar, Appointment and Setting table + ## v1.2 -- upgrade database schema as defined in lib/customerdb.sql +- upgrade database schema as defined in `lib/customerdb.sql` - add column 'customer_id' to Appointment table - add column 'from_customer_id' and 'for_customer_id' to Voucher table - replace all old files except conf.php ## v1.1 -- upgrade database schema as defined in lib/customerdb.sql +- upgrade database schema as defined in `lib/customerdb.sql` - add table Appointment - add table Calendar - add new table's indices and constraints diff --git a/docs/decisions/0001-UTC Timestamps.md b/docs/decisions/0001-UTC Timestamps.md new file mode 100644 index 0000000..a5346f2 --- /dev/null +++ b/docs/decisions/0001-UTC Timestamps.md @@ -0,0 +1,18 @@ +# Agent-Server Communication Method +Architecture Decision Record +Lang: en +Encoding: utf-8 +Date: 2022-11-01 +Author: Georg Sieber + +## Decision +All timestamps are stored in UTC time in the database on the server. + +## Status +Accepted + +## Context +Since the Customer Database app targets users all around the world, timestamps must be in UTC time to avoid time zone conflicts. + +## Consequences +All communication over the API must also use UTC timestamps. diff --git a/lib/api-data.php b/lib/api-data.php index 723bd05..9c0ce63 100644 --- a/lib/api-data.php +++ b/lib/api-data.php @@ -77,10 +77,18 @@ function handleApiRequestData($srcdata) { switch($srcdata['method']) { case 'customerdb.read': - $customers = $db->getCustomersByClient($userId); - $vouchers = $db->getVouchersByClient($userId); - $calendars = $db->getCalendarsByClient($userId); - $appointments = $db->getAppointmentsByClient($userId); + $diffSince = null; + if(isset($srcdata['params']['diff_since'])) $diffSince = strtotime($srcdata['params']['diff_since']); + if($diffSince === false) { // strtotime() returns false in case of parsing error + $resdata['result'] = null; + $resdata['error'] = 'Invalid Date'; + break; + } + + $customers = $db->getCustomersByClient($userId, date('Y-m-d H:i:s', $diffSince)); + $vouchers = $db->getVouchersByClient($userId, date('Y-m-d H:i:s', $diffSince)); + $calendars = $db->getCalendarsByClient($userId, date('Y-m-d H:i:s', $diffSince)); + $appointments = $db->getAppointmentsByClient($userId, date('Y-m-d H:i:s', $diffSince)); foreach($customers as $customer) { if($customer->image != null) $customer->image = base64_encode($customer->image); @@ -93,6 +101,7 @@ function handleApiRequestData($srcdata) { 'calendars' => $calendars, 'appointments' => $appointments, ]; + #error_log(count($customers).' customers changed since '.date('Y-m-d H:i:s',$diffSince)); // debug break; case 'customerdb.put': @@ -100,6 +109,7 @@ function handleApiRequestData($srcdata) { $db->getDbHandle()->beginTransaction(); // todo check if all attr delivered before accessing it in array + #error_log(count($srcdata['params']['customers']).' customers put'); // debug foreach($srcdata['params']['customers'] as $customer) { foreach(['id', 'title', 'first_name', 'last_name', 'phone_home', 'phone_work', 'email', 'street', 'zipcode', 'city', 'country', 'customer_group', diff --git a/lib/db.php b/lib/db.php index e296cff..a898f98 100644 --- a/lib/db.php +++ b/lib/db.php @@ -34,6 +34,10 @@ public function existsSchema() { } } + private static function currentUtcDateTime() { + return gmdate('Y-m-d H:i:s'); + } + // Client Operations public function getClient($id) { $this->stmt = $this->dbh->prepare( @@ -123,12 +127,13 @@ public function deleteClient($id) { } // Customer Operations - public function getCustomersByClient($clientId) { + public function getCustomersByClient($clientId, $diffSince=null) { + if(!$diffSince) $diffSince = '1970-01-01 00:00:00'; $this->stmt = $this->dbh->prepare( 'SELECT id, title, first_name, last_name, phone_home, phone_mobile, phone_work, email, street, zipcode, city, country, birthday, customer_group, newsletter, notes, custom_fields, image, consent, files, last_modified, removed - FROM Customer WHERE client_id = :client_id' + FROM Customer WHERE client_id = :client_id AND last_modified_on_server > :diff_since' ); - $this->stmt->execute([':client_id' => $clientId]); + $this->stmt->execute([':client_id' => $clientId, ':diff_since' => $diffSince]); return $this->stmt->fetchAll(PDO::FETCH_CLASS, 'Customer'); } public function getActiveCustomersByClient($clientId) { @@ -152,10 +157,10 @@ public function getActiveCustomerByClient($clientId, $id) { public function markDeletedCustomerByClient($clientId, $id) { $this->stmt = $this->dbh->prepare( 'UPDATE Customer - SET title = "", first_name = "", last_name = "", phone_home = "", phone_mobile = "", phone_work = "", email = "", street = "", zipcode = "", city = "", country = "", birthday = NULL, customer_group = "", newsletter = 0, notes = "", custom_fields = "", image = NULL, consent = NULL, files = NULL, last_modified = CURRENT_TIMESTAMP, removed = 1 + SET title = "", first_name = "", last_name = "", phone_home = "", phone_mobile = "", phone_work = "", email = "", street = "", zipcode = "", city = "", country = "", birthday = NULL, customer_group = "", newsletter = 0, notes = "", custom_fields = "", image = NULL, consent = NULL, files = NULL, last_modified = :current_utc_time, last_modified_on_server = :current_utc_time, removed = 1 WHERE client_id = :client_id AND id = :id' ); - return $this->stmt->execute([':client_id' => $clientId, ':id' => $id]); + return $this->stmt->execute([':client_id' => $clientId, ':id' => $id, ':current_utc_time' => self::currentUtcDateTime()]); } public function insertUpdateCustomer($clientId, $id, $title, $firstName, $lastName, $phoneHome, $phoneMobile, $phoneWork, $email, $street, $zipcode, $city, $country, $birthday, $customerGroup, $newsletter, $notes, $customFields, $image, $consentImage, $files, $lastModified, $removed) { @@ -170,7 +175,7 @@ public function insertUpdateCustomer($clientId, $id, $title, $firstName, $lastNa // update if last_modified is newer than in stored record $this->stmt = $this->dbh->prepare( - 'UPDATE Customer SET title = :title, first_name = :first_name, last_name = :last_name, phone_home = :phone_home, phone_mobile = :phone_mobile, phone_work = :phone_work, email = :email, street = :street, zipcode = :zipcode, city = :city, country = :country, birthday = :birthday, customer_group = :customer_group, newsletter = :newsletter, notes = :notes, custom_fields = :custom_fields, image = :image, consent = :consent, files = :files, last_modified = :last_modified, removed = :removed + 'UPDATE Customer SET title = :title, first_name = :first_name, last_name = :last_name, phone_home = :phone_home, phone_mobile = :phone_mobile, phone_work = :phone_work, email = :email, street = :street, zipcode = :zipcode, city = :city, country = :country, birthday = :birthday, customer_group = :customer_group, newsletter = :newsletter, notes = :notes, custom_fields = :custom_fields, image = :image, consent = :consent, files = :files, last_modified = :last_modified, last_modified_on_server = :last_modified_on_server, removed = :removed WHERE client_id = :client_id AND id = :id AND last_modified < :last_modified' ); return $this->stmt->execute([ @@ -196,6 +201,7 @@ public function insertUpdateCustomer($clientId, $id, $title, $firstName, $lastNa ':consent' => $consentImage, ':files' => $files, ':last_modified' => $lastModified, + ':last_modified_on_server' => self::currentUtcDateTime(), ':removed' => $removed, ]); @@ -203,8 +209,8 @@ public function insertUpdateCustomer($clientId, $id, $title, $firstName, $lastNa // create new record $this->stmt = $this->dbh->prepare( - 'INSERT INTO Customer (client_id, id, title, first_name, last_name, phone_home, phone_mobile, phone_work, email, street, zipcode, city, country, birthday, customer_group, newsletter, notes, custom_fields, image, consent, files, last_modified, removed) - VALUES (:client_id, :id, :title, :first_name, :last_name, :phone_home, :phone_mobile, :phone_work, :email, :street, :zipcode, :city, :country, :birthday, :customer_group, :newsletter, :notes, :custom_fields, :image, :consent, :files, :last_modified, :removed)' + 'INSERT INTO Customer (client_id, id, title, first_name, last_name, phone_home, phone_mobile, phone_work, email, street, zipcode, city, country, birthday, customer_group, newsletter, notes, custom_fields, image, consent, files, last_modified, last_modified_on_server, removed) + VALUES (:client_id, :id, :title, :first_name, :last_name, :phone_home, :phone_mobile, :phone_work, :email, :street, :zipcode, :city, :country, :birthday, :customer_group, :newsletter, :notes, :custom_fields, :image, :consent, :files, :last_modified, :last_modified_on_server, :removed)' ); return $this->stmt->execute([ ':client_id' => $clientId, @@ -229,6 +235,7 @@ public function insertUpdateCustomer($clientId, $id, $title, $firstName, $lastNa ':consent' => $consentImage, ':files' => $files, ':last_modified' => $lastModified, + ':last_modified_on_server' => self::currentUtcDateTime(), ':removed' => $removed, ]); @@ -236,11 +243,12 @@ public function insertUpdateCustomer($clientId, $id, $title, $firstName, $lastNa } // Voucher Operations - public function getVouchersByClient($clientId) { + public function getVouchersByClient($clientId, $diffSince=null) { + if(!$diffSince) $diffSince = '1970-01-01 00:00:00'; $this->stmt = $this->dbh->prepare( - 'SELECT * FROM Voucher WHERE client_id = :client_id' + 'SELECT * FROM Voucher WHERE client_id = :client_id AND last_modified_on_server > :diff_since' ); - $this->stmt->execute([':client_id' => $clientId]); + $this->stmt->execute([':client_id' => $clientId, ':diff_since' => $diffSince]); return $this->stmt->fetchAll(PDO::FETCH_CLASS, 'Voucher', [$this->getCurrencyByClient($clientId)]); } public function getActiveVouchersByClient($clientId) { @@ -262,10 +270,10 @@ public function getActiveVoucherByClient($clientId, $id) { public function markDeletedVoucherByClient($clientId, $id) { $this->stmt = $this->dbh->prepare( 'UPDATE Voucher - SET original_value = 0, current_value = 0, voucher_no = "", from_customer = "", from_customer_id = NULL, for_customer = "", for_customer_id = NULL, valid_until = NULL, redeemed = NULL, notes = "", last_modified = CURRENT_TIMESTAMP, removed = 1 + SET original_value = 0, current_value = 0, voucher_no = "", from_customer = "", from_customer_id = NULL, for_customer = "", for_customer_id = NULL, valid_until = NULL, redeemed = NULL, notes = "", last_modified = :current_utc_time, last_modified_on_server = :current_utc_time, removed = 1 WHERE client_id = :client_id AND id = :id' ); - return $this->stmt->execute([':client_id' => $clientId, ':id' => $id]); + return $this->stmt->execute([':client_id' => $clientId, ':id' => $id, ':current_utc_time' => self::currentUtcDateTime()]); } public function insertUpdateVoucher($clientId, $id, $originalValue, $currentValue, $voucherNo, $fromCustomer, $fromCustomerId, $forCustomer, $forCustomerId, $issued, $validUntil, $redeemed, $notes, $lastModified, $removed) { @@ -280,7 +288,7 @@ public function insertUpdateVoucher($clientId, $id, $originalValue, $currentValu // update if last_modified is newer than in stored record $this->stmt = $this->dbh->prepare( - 'UPDATE Voucher SET original_value = :original_value, current_value = :current_value, voucher_no = :voucher_no, from_customer = :from_customer, from_customer_id = :from_customer_id, for_customer = :for_customer, for_customer_id = :for_customer_id, issued = :issued, valid_until = :valid_until, redeemed = :redeemed, notes = :notes, last_modified = :last_modified, removed = :removed + 'UPDATE Voucher SET original_value = :original_value, current_value = :current_value, voucher_no = :voucher_no, from_customer = :from_customer, from_customer_id = :from_customer_id, for_customer = :for_customer, for_customer_id = :for_customer_id, issued = :issued, valid_until = :valid_until, redeemed = :redeemed, notes = :notes, last_modified = :last_modified, last_modified_on_server = :last_modified_on_server, removed = :removed WHERE client_id = :client_id AND id = :id AND last_modified < :last_modified' ); return $this->stmt->execute([ @@ -298,6 +306,7 @@ public function insertUpdateVoucher($clientId, $id, $originalValue, $currentValu ':redeemed' => $redeemed, ':notes' => $notes, ':last_modified' => $lastModified, + ':last_modified_on_server' => self::currentUtcDateTime(), ':removed' => $removed, ]); @@ -305,8 +314,8 @@ public function insertUpdateVoucher($clientId, $id, $originalValue, $currentValu // create new record $this->stmt = $this->dbh->prepare( - 'INSERT INTO Voucher (client_id, id, original_value, current_value, voucher_no, from_customer, from_customer_id, for_customer, for_customer_id, issued, valid_until, redeemed, notes, last_modified, removed) - VALUES (:client_id, :id, :original_value, :current_value, :voucher_no, :from_customer, :from_customer_id, :for_customer, :for_customer_id, :issued, :valid_until, :redeemed, :notes, :last_modified, :removed)' + 'INSERT INTO Voucher (client_id, id, original_value, current_value, voucher_no, from_customer, from_customer_id, for_customer, for_customer_id, issued, valid_until, redeemed, notes, last_modified, last_modified_on_server, removed) + VALUES (:client_id, :id, :original_value, :current_value, :voucher_no, :from_customer, :from_customer_id, :for_customer, :for_customer_id, :issued, :valid_until, :redeemed, :notes, :last_modified, :last_modified_on_server, :removed)' ); return $this->stmt->execute([ ':client_id' => $clientId, @@ -323,6 +332,7 @@ public function insertUpdateVoucher($clientId, $id, $originalValue, $currentValu ':redeemed' => $redeemed, ':notes' => $notes, ':last_modified' => $lastModified, + ':last_modified_on_server' => self::currentUtcDateTime(), ':removed' => $removed, ]); @@ -330,11 +340,12 @@ public function insertUpdateVoucher($clientId, $id, $originalValue, $currentValu } // Appointment Operations - public function getAppointmentsByClient($clientId) { + public function getAppointmentsByClient($clientId, $diffSince=null) { + if(!$diffSince) $diffSince = '1970-01-01 00:00:00'; $this->stmt = $this->dbh->prepare( - 'SELECT * FROM Appointment WHERE client_id = :id' + 'SELECT * FROM Appointment WHERE client_id = :id AND last_modified_on_server > :diff_since' ); - $this->stmt->execute([':id' => $clientId]); + $this->stmt->execute([':id' => $clientId, ':diff_since' => $diffSince]); return $this->stmt->fetchAll(); } public function getActiveAppointmentsByClient($clientId) { @@ -357,7 +368,7 @@ public function insertUpdateAppointment($clientId, $id, $calendarId, $title, $no // update if last_modified is newer than in stored record $this->stmt = $this->dbh->prepare( - 'UPDATE Appointment SET calendar_id = :calendar_id, title = :title, notes = :notes, time_start = :time_start, time_end = :time_end, fullday = :fullday, customer = :customer, customer_id = :customer_id, location = :location, last_modified = :last_modified, removed = :removed + 'UPDATE Appointment SET calendar_id = :calendar_id, title = :title, notes = :notes, time_start = :time_start, time_end = :time_end, fullday = :fullday, customer = :customer, customer_id = :customer_id, location = :location, last_modified = :last_modified, last_modified_on_server = :last_modified_on_server, removed = :removed WHERE client_id = :client_id AND id = :id AND last_modified < :last_modified' ); return $this->stmt->execute([ @@ -373,6 +384,7 @@ public function insertUpdateAppointment($clientId, $id, $calendarId, $title, $no ':customer_id' => $customerId, ':location' => $location, ':last_modified' => $lastModified, + ':last_modified_on_server' => self::currentUtcDateTime(), ':removed' => $removed, ]); @@ -380,8 +392,8 @@ public function insertUpdateAppointment($clientId, $id, $calendarId, $title, $no // create new record $this->stmt = $this->dbh->prepare( - 'INSERT INTO Appointment (client_id, id, calendar_id, title, notes, time_start, time_end, fullday, customer, customer_id, location, last_modified, removed) - VALUES (:client_id, :id, :calendar_id, :title, :notes, :time_start, :time_end, :fullday, :customer, :customer_id, :location, :last_modified, :removed)' + 'INSERT INTO Appointment (client_id, id, calendar_id, title, notes, time_start, time_end, fullday, customer, customer_id, location, last_modified, last_modified_on_server, removed) + VALUES (:client_id, :id, :calendar_id, :title, :notes, :time_start, :time_end, :fullday, :customer, :customer_id, :location, :last_modified, :last_modified_on_server, :removed)' ); return $this->stmt->execute([ ':client_id' => $clientId, @@ -396,6 +408,7 @@ public function insertUpdateAppointment($clientId, $id, $calendarId, $title, $no ':customer_id' => $customerId, ':location' => $location, ':last_modified' => $lastModified, + ':last_modified_on_server' => self::currentUtcDateTime(), ':removed' => $removed, ]); @@ -403,11 +416,12 @@ public function insertUpdateAppointment($clientId, $id, $calendarId, $title, $no } // Calendar Operations - public function getCalendarsByClient($clientId) { + public function getCalendarsByClient($clientId, $diffSince=null) { + if(!$diffSince) $diffSince = '1970-01-01 00:00:00'; $this->stmt = $this->dbh->prepare( - 'SELECT * FROM Calendar WHERE client_id = :id' + 'SELECT * FROM Calendar WHERE client_id = :id AND last_modified_on_server > :diff_since' ); - $this->stmt->execute([':id' => $clientId]); + $this->stmt->execute([':id' => $clientId, ':diff_since' => $diffSince]); return $this->stmt->fetchAll(); } public function getActiveCalendarsByClient($clientId) { @@ -430,7 +444,7 @@ public function insertUpdateCalendar($clientId, $id, $title, $color, $notes, $la // update if last_modified is newer than in stored record $this->stmt = $this->dbh->prepare( - 'UPDATE Calendar SET title = :title, color = :color, notes = :notes, last_modified = :last_modified, removed = :removed + 'UPDATE Calendar SET title = :title, color = :color, notes = :notes, last_modified = :last_modified, last_modified_on_server = :last_modified_on_server, removed = :removed WHERE client_id = :client_id AND id = :id AND last_modified < :last_modified' ); return $this->stmt->execute([ @@ -440,6 +454,7 @@ public function insertUpdateCalendar($clientId, $id, $title, $color, $notes, $la ':color' => $color, ':notes' => $notes, ':last_modified' => $lastModified, + ':last_modified_on_server' => self::currentUtcDateTime(), ':removed' => $removed, ]); @@ -447,8 +462,8 @@ public function insertUpdateCalendar($clientId, $id, $title, $color, $notes, $la // create new record $this->stmt = $this->dbh->prepare( - 'INSERT INTO Calendar (client_id, id, title, color, notes, last_modified, removed) - VALUES (:client_id, :id, :title, :color, :notes, :last_modified, :removed)' + 'INSERT INTO Calendar (client_id, id, title, color, notes, last_modified, last_modified_on_server, removed) + VALUES (:client_id, :id, :title, :color, :notes, :last_modified, :last_modified_on_server, :removed)' ); return $this->stmt->execute([ ':client_id' => $clientId, @@ -457,6 +472,7 @@ public function insertUpdateCalendar($clientId, $id, $title, $color, $notes, $la ':color' => $color, ':notes' => $notes, ':last_modified' => $lastModified, + ':last_modified_on_server' => self::currentUtcDateTime(), ':removed' => $removed, ]); diff --git a/lib/sql/customerdb.sql b/sql/customerdb.sql similarity index 93% rename from lib/sql/customerdb.sql rename to sql/customerdb.sql index 1479972..5c07e34 100644 --- a/lib/sql/customerdb.sql +++ b/sql/customerdb.sql @@ -41,6 +41,7 @@ CREATE TABLE `Appointment` ( `customer_id` bigint(11) DEFAULT NULL, `location` text NOT NULL, `last_modified` datetime NOT NULL DEFAULT current_timestamp(), + `last_modified_on_server` datetime NOT NULL DEFAULT current_timestamp(), `removed` tinyint(4) NOT NULL DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; @@ -57,6 +58,7 @@ CREATE TABLE `Calendar` ( `color` text NOT NULL, `notes` text NOT NULL, `last_modified` datetime NOT NULL DEFAULT current_timestamp(), + `last_modified_on_server` datetime NOT NULL DEFAULT current_timestamp(), `removed` tinyint(4) NOT NULL DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; @@ -106,6 +108,7 @@ CREATE TABLE `Customer` ( `consent` longblob DEFAULT NULL, `files` longblob DEFAULT NULL, `last_modified` datetime NOT NULL DEFAULT current_timestamp(), + `last_modified_on_server` datetime NOT NULL DEFAULT current_timestamp(), `removed` tinyint(4) NOT NULL DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=latin1; @@ -120,7 +123,8 @@ CREATE TABLE `Setting` ( `client_id` int(11) NOT NULL, `setting` text NOT NULL, `value` text NOT NULL, - `last_modified` datetime NOT NULL DEFAULT current_timestamp() + `last_modified` datetime NOT NULL DEFAULT current_timestamp(), + `last_modified_on_server` datetime NOT NULL DEFAULT current_timestamp() ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- -------------------------------------------------------- @@ -144,6 +148,7 @@ CREATE TABLE `Voucher` ( `redeemed` datetime DEFAULT NULL, `notes` text CHARACTER SET utf8mb4 NOT NULL, `last_modified` datetime NOT NULL DEFAULT current_timestamp(), + `last_modified_on_server` datetime NOT NULL DEFAULT current_timestamp(), `removed` tinyint(4) NOT NULL DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=latin1;