20260428120007_create_api_tokens.php 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. <?php
  2. declare(strict_types=1);
  3. use App\Infrastructure\Db\Migrations\BaseMigration;
  4. /**
  5. * api_tokens has a CHECK constraint correlating `kind` with which of
  6. * `reporter_id` / `consumer_id` is set. Phinx's high-level API doesn't
  7. * expose CHECK constraints directly, and SQLite cannot add CHECK via
  8. * ALTER TABLE — so this migration writes raw CREATE TABLE per adapter.
  9. */
  10. final class CreateApiTokens extends BaseMigration
  11. {
  12. public function up(): void
  13. {
  14. if ($this->isMysql()) {
  15. $this->execute(<<<'SQL'
  16. CREATE TABLE api_tokens (
  17. id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  18. token_hash VARCHAR(64) NOT NULL,
  19. token_prefix VARCHAR(16) NOT NULL,
  20. kind VARCHAR(32) NOT NULL,
  21. reporter_id INT UNSIGNED NULL,
  22. consumer_id INT UNSIGNED NULL,
  23. expires_at DATETIME(6) NULL,
  24. revoked_at DATETIME(6) NULL,
  25. last_used_at DATETIME(6) NULL,
  26. created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  27. CONSTRAINT chk_api_tokens_kind CHECK (
  28. (kind = 'reporter' AND reporter_id IS NOT NULL AND consumer_id IS NULL) OR
  29. (kind = 'consumer' AND consumer_id IS NOT NULL AND reporter_id IS NULL) OR
  30. (kind IN ('admin', 'service') AND reporter_id IS NULL AND consumer_id IS NULL)
  31. ),
  32. CONSTRAINT fk_api_tokens_reporter FOREIGN KEY (reporter_id)
  33. REFERENCES reporters(id) ON DELETE CASCADE,
  34. CONSTRAINT fk_api_tokens_consumer FOREIGN KEY (consumer_id)
  35. REFERENCES consumers(id) ON DELETE CASCADE,
  36. UNIQUE KEY uniq_api_tokens_hash (token_hash),
  37. KEY idx_api_tokens_kind (kind),
  38. KEY idx_api_tokens_reporter_id (reporter_id),
  39. KEY idx_api_tokens_consumer_id (consumer_id),
  40. KEY idx_api_tokens_revoked_at (revoked_at)
  41. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  42. SQL);
  43. return;
  44. }
  45. // SQLite path
  46. $this->execute(<<<'SQL'
  47. CREATE TABLE api_tokens (
  48. id INTEGER PRIMARY KEY AUTOINCREMENT,
  49. token_hash VARCHAR(64) NOT NULL,
  50. token_prefix VARCHAR(16) NOT NULL,
  51. kind VARCHAR(32) NOT NULL,
  52. reporter_id INTEGER NULL,
  53. consumer_id INTEGER NULL,
  54. expires_at TEXT NULL,
  55. revoked_at TEXT NULL,
  56. last_used_at TEXT NULL,
  57. created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
  58. CONSTRAINT chk_api_tokens_kind CHECK (
  59. (kind = 'reporter' AND reporter_id IS NOT NULL AND consumer_id IS NULL) OR
  60. (kind = 'consumer' AND consumer_id IS NOT NULL AND reporter_id IS NULL) OR
  61. (kind IN ('admin', 'service') AND reporter_id IS NULL AND consumer_id IS NULL)
  62. ),
  63. FOREIGN KEY (reporter_id) REFERENCES reporters(id) ON DELETE CASCADE,
  64. FOREIGN KEY (consumer_id) REFERENCES consumers(id) ON DELETE CASCADE
  65. )
  66. SQL);
  67. $this->execute('CREATE UNIQUE INDEX uniq_api_tokens_hash ON api_tokens(token_hash)');
  68. $this->execute('CREATE INDEX idx_api_tokens_kind ON api_tokens(kind)');
  69. $this->execute('CREATE INDEX idx_api_tokens_reporter_id ON api_tokens(reporter_id)');
  70. $this->execute('CREATE INDEX idx_api_tokens_consumer_id ON api_tokens(consumer_id)');
  71. $this->execute('CREATE INDEX idx_api_tokens_revoked_at ON api_tokens(revoked_at)');
  72. }
  73. public function down(): void
  74. {
  75. $this->execute('DROP TABLE IF EXISTS api_tokens');
  76. }
  77. }