migrate_water_mini_api.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. <?php
  2. /** Additive API schema. No sample users, grants, balances or paid orders are created. */
  3. require dirname(__DIR__) . '/vendor/autoload.php';
  4. \Dotenv\Dotenv::createUnsafeMutable(dirname(__DIR__))->load();
  5. \support\App::loadAllConfig(['route']);
  6. if (!in_array('--apply', $argv, true)) {
  7. echo "Plan: add eight mini API tables, preserving existing water data.\n";
  8. exit;
  9. }
  10. $db = (new \app\miniapi\model\MiniUser())->getConnection();
  11. $definitions = [
  12. 'user' => "id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, appid VARCHAR(64) NOT NULL, openid VARCHAR(128) NOT NULL,
  13. nickname VARCHAR(60) NOT NULL DEFAULT '', status TINYINT NOT NULL DEFAULT 1, created_at DATETIME NOT NULL,
  14. updated_at DATETIME NOT NULL, UNIQUE KEY identity_key(appid,openid)",
  15. 'session' => "id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id BIGINT UNSIGNED NOT NULL,
  16. token_hash CHAR(64) NOT NULL, expires_at DATETIME NOT NULL, revoked_at DATETIME NULL, created_at DATETIME NOT NULL,
  17. UNIQUE KEY token_key(token_hash), KEY user_sessions(user_id,expires_at)",
  18. 'grant' => "id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id BIGINT UNSIGNED NOT NULL,
  19. role VARCHAR(30) NOT NULL, resource_id BIGINT UNSIGNED NOT NULL, status TINYINT NOT NULL DEFAULT 1,
  20. created_by BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL,
  21. UNIQUE KEY user_role_resource(user_id,role,resource_id)",
  22. 'command' => "id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id BIGINT UNSIGNED NOT NULL,
  23. request_key VARCHAR(80) NOT NULL, action VARCHAR(100) NOT NULL, payload_hash CHAR(64) NOT NULL,
  24. result LONGTEXT NOT NULL, created_at DATETIME NOT NULL, UNIQUE KEY command_key(user_id,request_key)",
  25. 'audit' => "id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, actor_type VARCHAR(20) NOT NULL,
  26. actor_id BIGINT UNSIGNED NOT NULL, action VARCHAR(100) NOT NULL, resource_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
  27. data LONGTEXT NOT NULL, created_at DATETIME NOT NULL, KEY actor_events(actor_type,actor_id,id)",
  28. 'checkout' => "id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id BIGINT UNSIGNED NOT NULL,
  29. enterprise_id BIGINT UNSIGNED NOT NULL, code VARCHAR(60) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'pending',
  30. items LONGTEXT NOT NULL, address_snapshot LONGTEXT NOT NULL, invoice LONGTEXT NULL,
  31. amount_cents BIGINT UNSIGNED NOT NULL, created_at DATETIME NOT NULL, expires_at DATETIME NOT NULL,
  32. UNIQUE KEY checkout_code(code), KEY enterprise_orders(enterprise_id,id)",
  33. 'enterprise_balance' => "id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, enterprise_id BIGINT UNSIGNED NOT NULL,
  34. balance_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL,
  35. UNIQUE KEY enterprise_balance_key(enterprise_id)",
  36. 'balance_ledger' => "id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, enterprise_id BIGINT UNSIGNED NOT NULL,
  37. checkout_id BIGINT UNSIGNED NOT NULL, user_id BIGINT UNSIGNED NOT NULL, request_key VARCHAR(80) NOT NULL,
  38. direction TINYINT NOT NULL, amount_cents BIGINT UNSIGNED NOT NULL, before_balance BIGINT UNSIGNED NOT NULL,
  39. after_balance BIGINT UNSIGNED NOT NULL, source VARCHAR(30) NOT NULL, created_at DATETIME NOT NULL,
  40. UNIQUE KEY balance_payment_key(enterprise_id,user_id,request_key), KEY balance_checkout(checkout_id,id)",
  41. 'task_record' => "id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, delivery_id BIGINT UNSIGNED NOT NULL,
  42. user_id BIGINT UNSIGNED NOT NULL, kind VARCHAR(30) NOT NULL, data LONGTEXT NOT NULL, created_at DATETIME NOT NULL,
  43. KEY delivery_records(delivery_id,id)",
  44. 'photo' => "id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, delivery_id BIGINT UNSIGNED NOT NULL,
  45. user_id BIGINT UNSIGNED NOT NULL, mime VARCHAR(30) NOT NULL, image_data MEDIUMBLOB NOT NULL,
  46. latitude DECIMAL(10,6) NULL, longitude DECIMAL(10,6) NULL, created_at DATETIME NOT NULL,
  47. KEY delivery_photos(delivery_id,id)",
  48. ];
  49. foreach ($definitions as $name => $definition) {
  50. $table = 'sa_water_mini_' . $name;
  51. $db->statement("CREATE TABLE IF NOT EXISTS `$table` ($definition) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
  52. $engine = $db->selectOne('SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?', [$table]);
  53. if (strtolower($engine->ENGINE ?? '') !== 'innodb') {
  54. throw new RuntimeException($table . ' must use InnoDB');
  55. }
  56. }
  57. $checkoutTable = 'sa_water_mini_checkout';
  58. $checkoutColumns = $db->select("SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?", [$checkoutTable]);
  59. $checkoutColumnNames = array_map(static fn ($row) => (string) ($row->COLUMN_NAME ?? ''), $checkoutColumns);
  60. if (!in_array('payment_method', $checkoutColumnNames, true)) {
  61. $db->statement("ALTER TABLE `$checkoutTable` ADD COLUMN payment_method VARCHAR(20) NULL AFTER expires_at");
  62. }
  63. if (!in_array('paid_at', $checkoutColumnNames, true)) {
  64. $db->statement("ALTER TABLE `$checkoutTable` ADD COLUMN paid_at DATETIME NULL AFTER payment_method");
  65. }
  66. $balanceLedgerTable = 'sa_water_mini_balance_ledger';
  67. $paymentIndexColumns = array_map(
  68. static fn ($row) => (string) ($row->Column_name ?? ''),
  69. $db->select("SHOW INDEX FROM `$balanceLedgerTable` WHERE Key_name='balance_payment_key'")
  70. );
  71. if ($paymentIndexColumns !== ['enterprise_id', 'user_id', 'request_key']) {
  72. if ($paymentIndexColumns) {
  73. $db->statement("ALTER TABLE `$balanceLedgerTable` DROP INDEX balance_payment_key");
  74. }
  75. $db->statement("ALTER TABLE `$balanceLedgerTable` ADD UNIQUE KEY balance_payment_key(enterprise_id,user_id,request_key)");
  76. }
  77. echo "Applied mini API tables and payment columns; existing business records and authorizations unchanged.\n";