Update: 2026-05-03 21:58:11
This commit is contained in:
105
scripts/migrate.php
Normal file
105
scripts/migrate.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/**
|
||||
* Advanced Migration Script: Schema Update + Data Encryption
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../app/bootstrap/init.php';
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Core\Encryption;
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
echo "--- Starting Security Migration ---\n";
|
||||
|
||||
// 1. Add email_hash column if it doesn't exist
|
||||
try {
|
||||
$db->exec("ALTER TABLE users ADD COLUMN email_hash VARCHAR(64) AFTER email, ADD INDEX (email_hash)");
|
||||
echo "[OK] Added email_hash column and index.\n";
|
||||
} catch (\Exception $e) {
|
||||
echo "[SKIP] email_hash column might already exist.\n";
|
||||
}
|
||||
|
||||
// 2. Fetch all users to encrypt their data
|
||||
$stmt = $db->query("SELECT id, name, email FROM users");
|
||||
$users = $stmt->fetchAll();
|
||||
|
||||
echo "Found " . count($users) . " users. Starting encryption...\n";
|
||||
|
||||
$updateStmt = $db->prepare("UPDATE users SET name = ?, email = ?, email_hash = ? WHERE id = ?");
|
||||
|
||||
foreach ($users as $user) {
|
||||
// Check if data is already encrypted (to avoid double encryption)
|
||||
$isAlreadyEncrypted = Encryption::decrypt($user['email']) !== false;
|
||||
|
||||
if ($isAlreadyEncrypted) {
|
||||
echo "User ID {$user['id']} is already encrypted. Skipping.\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Encrypt Name
|
||||
$encryptedName = Encryption::encrypt($user['name']);
|
||||
|
||||
// Encrypt Email
|
||||
$encryptedEmail = Encryption::encrypt($user['email']);
|
||||
|
||||
// Generate Hash for lookup
|
||||
$emailHash = hash('sha256', strtolower($user['email']));
|
||||
|
||||
$updateStmt->execute([
|
||||
$encryptedName,
|
||||
$encryptedEmail,
|
||||
$emailHash,
|
||||
$user['id']
|
||||
]);
|
||||
|
||||
echo "User ID {$user['id']} migrated successfully.\n";
|
||||
}
|
||||
|
||||
// 3. Create companies table (Updated to match production schema)
|
||||
try {
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS companies (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_id INT,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
name_en VARCHAR(255),
|
||||
tax_identification_number VARCHAR(50),
|
||||
commercial_registration_number VARCHAR(50),
|
||||
address TEXT,
|
||||
city VARCHAR(100),
|
||||
contact_email VARCHAR(255),
|
||||
contact_phone VARCHAR(50),
|
||||
jofotara_client_id_encrypted TEXT,
|
||||
jofotara_secret_key_encrypted TEXT,
|
||||
jofotara_income_source_sequence VARCHAR(50),
|
||||
certificate_path VARCHAR(255),
|
||||
certificate_password_encrypted TEXT,
|
||||
is_jofotara_linked TINYINT(1) DEFAULT 0,
|
||||
is_active TINYINT(1) DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at DATETIME DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
||||
echo "[OK] Companies table synchronized with production schema.\n";
|
||||
} catch (\Exception $e) {
|
||||
echo "[ERROR] Synchronizing companies table: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
// 4. Create user_companies pivot table
|
||||
try {
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS user_companies (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
company_id INT NOT NULL,
|
||||
role VARCHAR(50) DEFAULT 'employee',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY user_company (user_id, company_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
||||
echo "[OK] User_companies table created or exists.\n";
|
||||
} catch (\Exception $e) {
|
||||
echo "[ERROR] Creating user_companies table: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
echo "--- Migration Complete ---\n";
|
||||
157
scripts/schema.sql
Normal file
157
scripts/schema.sql
Normal file
@@ -0,0 +1,157 @@
|
||||
-- ════════════════════════════════════════════════════════════
|
||||
-- مُصادَق — Database Schema v1.0
|
||||
-- ════════════════════════════════════════════════════════════
|
||||
|
||||
-- Tenants (Accounting Offices)
|
||||
CREATE TABLE tenants (
|
||||
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
phone VARCHAR(20),
|
||||
status ENUM('active', 'suspended', 'trial') DEFAULT 'trial',
|
||||
trial_ends_at DATETIME,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Users
|
||||
CREATE TABLE users (
|
||||
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
tenant_id CHAR(36) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
role ENUM('super_admin','admin','accountant','viewer') NOT NULL,
|
||||
company_id CHAR(36) NULL, -- assigned company for accountant
|
||||
refresh_token_hash VARCHAR(255) NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
last_login_at DATETIME NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_tenant_email (tenant_id, email),
|
||||
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- API Keys (for external integrations / mobile scanner)
|
||||
CREATE TABLE api_keys (
|
||||
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
tenant_id CHAR(36) NOT NULL,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
public_key VARCHAR(64) NOT NULL UNIQUE,
|
||||
secret_hash VARCHAR(255) NOT NULL, -- bcrypt hash of secret
|
||||
last_used_at DATETIME NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Companies
|
||||
CREATE TABLE companies (
|
||||
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
tenant_id CHAR(36) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
name_en VARCHAR(255) NULL,
|
||||
tax_identification_number VARCHAR(20) NOT NULL,
|
||||
address TEXT NULL,
|
||||
jofotara_client_id_encrypted TEXT NULL,
|
||||
jofotara_secret_key_encrypted TEXT NULL,
|
||||
jofotara_income_source_sequence VARCHAR(50) NULL,
|
||||
certificate_path VARCHAR(255) NULL,
|
||||
certificate_password_encrypted VARCHAR(500) NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_name (name),
|
||||
INDEX idx_tin (tax_identification_number),
|
||||
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Subscriptions
|
||||
CREATE TABLE subscriptions (
|
||||
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
tenant_id CHAR(36) NOT NULL UNIQUE,
|
||||
plan ENUM('basic','office','pro','enterprise') NOT NULL DEFAULT 'basic',
|
||||
max_companies INT NOT NULL DEFAULT 5,
|
||||
max_invoices_per_month INT NOT NULL DEFAULT 100,
|
||||
price_jod DECIMAL(10,2) NOT NULL DEFAULT 0,
|
||||
invoices_used_this_month INT NOT NULL DEFAULT 0,
|
||||
status ENUM('active','past_due','cancelled') DEFAULT 'active',
|
||||
current_period_start DATETIME NULL,
|
||||
current_period_end DATETIME NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Invoices
|
||||
CREATE TABLE invoices (
|
||||
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
tenant_id CHAR(36) NOT NULL,
|
||||
company_id CHAR(36) NOT NULL,
|
||||
invoice_number VARCHAR(100) NULL,
|
||||
invoice_date DATE NULL,
|
||||
invoice_type ENUM('cash','credit') DEFAULT 'cash',
|
||||
ubl_type_code CHAR(3) DEFAULT '388',
|
||||
payment_method_code CHAR(3) DEFAULT '013',
|
||||
supplier_tin VARCHAR(20) NULL,
|
||||
supplier_name VARCHAR(255) NULL,
|
||||
supplier_address TEXT NULL,
|
||||
buyer_tin VARCHAR(20) NULL,
|
||||
buyer_national_id VARCHAR(20) NULL,
|
||||
buyer_name VARCHAR(255) NULL,
|
||||
subtotal DECIMAL(15,3) DEFAULT 0,
|
||||
discount_total DECIMAL(15,3) DEFAULT 0,
|
||||
tax_amount DECIMAL(15,3) DEFAULT 0,
|
||||
grand_total DECIMAL(15,3) DEFAULT 0,
|
||||
currency_code CHAR(3) DEFAULT 'JOD',
|
||||
status ENUM('uploaded','extracting','extracted','validated','validation_failed','submitting','approved','rejected') DEFAULT 'uploaded',
|
||||
original_file_path TEXT NULL,
|
||||
invoice_category VARCHAR(20) DEFAULT 'simplified',
|
||||
validation_errors JSON NULL,
|
||||
qr_code TEXT NULL,
|
||||
ai_confidence_score DECIMAL(4,3) NULL,
|
||||
ai_prompt_tokens INT DEFAULT 0,
|
||||
ai_completion_tokens INT DEFAULT 0,
|
||||
ai_total_cost DECIMAL(10,6) DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_tenant (tenant_id),
|
||||
INDEX idx_company (company_id),
|
||||
INDEX idx_status (status),
|
||||
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Invoice Lines
|
||||
CREATE TABLE invoice_lines (
|
||||
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
invoice_id CHAR(36) NOT NULL,
|
||||
line_number INT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
quantity DECIMAL(15,3) NOT NULL,
|
||||
unit_price DECIMAL(15,3) NOT NULL,
|
||||
discount DECIMAL(15,3) DEFAULT 0,
|
||||
tax_rate DECIMAL(5,4) NOT NULL,
|
||||
line_total DECIMAL(15,3) NOT NULL,
|
||||
FOREIGN KEY (invoice_id) REFERENCES invoices(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Audit Logs
|
||||
CREATE TABLE audit_logs (
|
||||
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
tenant_id CHAR(36) NULL,
|
||||
user_id CHAR(36) NULL,
|
||||
action VARCHAR(100) NOT NULL,
|
||||
entity_type VARCHAR(50) NULL,
|
||||
entity_id CHAR(36) NULL,
|
||||
old_data JSON NULL,
|
||||
new_data JSON NULL,
|
||||
ip_address VARCHAR(45) NULL,
|
||||
user_agent VARCHAR(500) NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_tenant (tenant_id),
|
||||
INDEX idx_action (action),
|
||||
INDEX idx_created (created_at)
|
||||
);
|
||||
Reference in New Issue
Block a user