import os
import stat
# ==============================================================================
# DEPLOYMENT SCRIPT FOR BLACK-CLOVER (v6 - CSRF Protection Removed)
# ==============================================================================
# This script generates the complete file and folder structure for the
# Black-Clover project. CSRF functionality has been removed to resolve
# persistent validation errors as requested.
#
# WARNING: This version is less secure against Cross-Site Request Forgery
# and is only suitable for strictly local, trusted network environments.
#
# To run:
# 1. Save this file as 'deploy.py'.
# 2. Run from the command line: python deploy.py
# ==============================================================================
PROJECT_NAME = "black-clover"
def create_file(path, content):
"""Creates a file with the given content, ensuring the directory exists."""
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
f.write(content.strip())
print(f"Created file: {path}")
# ==============================================================================
# --- FILE CONTENTS ---
# ==============================================================================
# --- Config (Outside Web Root) ---
config_php_content = r''' 0, 'path' => '/', 'domain' => $_SERVER['HTTP_HOST'], 'secure' => isset($_SERVER['HTTPS']), 'httponly' => true, 'samesite' => 'Strict']; session_set_cookie_params($p); session_start(); }
start_secure_session();
function manage_session_security() { if (is_logged_in() && isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity']) > SESSION_TIMEOUT_SECONDS) { session_unset(); session_destroy(); start_secure_session(); if (is_api_request()) { header('Content-Type: application/json'); http_response_code(401); echo json_encode(['error' => 'Session expired due to inactivity.']); exit; } return; } if (is_logged_in()) { $_SESSION['last_activity'] = time(); } }
manage_session_security();
function get_db_connection() { static $pdo = null; if ($pdo === null) { try { $db_dir = dirname(DB_PATH); if (!is_dir($db_dir)) mkdir($db_dir, 0750, true); $pdo = new PDO('sqlite:' . DB_PATH); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); $pdo->exec('PRAGMA journal_mode = WAL;'); } catch (PDOException $e) { die("Database connection failed: " . $e->getMessage()); } } return $pdo; }
function is_setup_needed() { return !file_exists(DB_PATH) || filesize(DB_PATH) === 0; }
'''
header_php_content = r''' 'Login', 'setup.php' => 'Initial Setup', 'dashboard.php' => 'Account Dashboard', 'user.php' => 'User Management', 'catagory-platform.php' => 'Manage Categories & Platforms', 'change-password.php' => 'Change Password', 'qrcode.php' => 'Scan QR Code', 'recovery.php' => 'Account Recovery' ];
$page_title = ($page_titles[$current_page] ?? 'Dashboard') . ' - Black-Clover';
$username = $_SESSION['username'] ?? '{Username}';
$nav_links = [ 'Scan QR' => ['link' => 'qrcode.php', 'icon' => 'fa-solid fa-qrcode'], 'Catagory-Platform' => ['link' => 'catagory-platform.php', 'icon' => 'fa-solid fa-tags'], 'Change Password' => ['link' => 'change-password.php', 'icon' => 'fa-solid fa-key'], 'Recover' => ['link' => 'recovery.php', 'icon' => 'fa-solid fa-shield-halved'], 'Add User' => ['link' => 'user.php', 'icon' => 'fa-solid fa-user-plus'] ];
?>
= htmlspecialchars($page_title) ?>
'''
footer_php_content = r'''
'''
# --- PHP API Handlers ---
cp_handler_php_content = r'''query("SELECT * FROM categories ORDER BY name")->fetchAll();
$platforms = $pdo->query("SELECT * FROM platforms ORDER BY name")->fetchAll();
echo json_encode(['categories' => $categories, 'platforms' => $platforms]);
break;
case 'POST':
$input = json_decode(file_get_contents('php://input'), true);
$type = $input['type'] ?? '';
$item = $input['item'] ?? null;
$name = isset($item['name']) ? trim($item['name']) : '';
$id = $item['id'] ?? null;
if (!($table = get_table_name($type)) || empty($name)) {
http_response_code(400); echo json_encode(['error' => 'Invalid input.']); exit;
}
if ($id) {
$stmt = $pdo->prepare("UPDATE {$table} SET name = ? WHERE id = ?");
$stmt->execute([$name, $id]);
} else {
$stmt = $pdo->prepare("INSERT INTO {$table} (name) VALUES (?)");
$stmt->execute([$name]);
}
echo json_encode(['success' => true]);
break;
case 'DELETE':
$input = json_decode(file_get_contents('php://input'), true);
$type = $input['type'] ?? '';
$id = $input['id'] ?? null;
if (!($table = get_table_name($type)) || !$id) {
http_response_code(400); echo json_encode(['error' => 'Invalid input.']); exit;
}
$stmt = $pdo->prepare("DELETE FROM {$table} WHERE id = ?");
$stmt->execute([$id]);
echo json_encode(['success' => $stmt->rowCount() > 0]);
break;
default:
http_response_code(405);
break;
}
} catch (PDOException $e) {
if ($e->getCode() == 23000) {
http_response_code(409); // 409 Conflict
echo json_encode(['error' => 'This name already exists. Please choose a different name.']);
} else {
error_log("CP Handler PDOException: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'A database error occurred.']);
}
} catch (Exception $e) {
error_log("CP Handler Exception: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'An internal server error occurred.']);
}
'''
setup_handler_php_content = r''' 'Setup has already been completed.']); exit; }
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); exit; }
$input = json_decode(file_get_contents('php://input'), true);
$username = $input['username'] ?? '';
$password = $input['password'] ?? '';
if (empty($username) || empty($password) || strlen($password) < 8) { http_response_code(400); echo json_encode(['error' => 'Username and a password of at least 8 characters are required.']); exit; }
try {
$pdo = get_db_connection();
$pdo->exec("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, kdf_salt BLOB NOT NULL, user_type TEXT NOT NULL DEFAULT 'admin', recovery_encrypted_mek BLOB, recovery_kdf_salt BLOB)");
$pdo->exec("CREATE TABLE IF NOT EXISTS accounts (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, encrypted_blob TEXT NOT NULL, FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE)");
$pdo->exec("CREATE TABLE IF NOT EXISTS categories (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL)");
$pdo->exec("CREATE TABLE IF NOT EXISTS platforms (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL)");
$password_hash = password_hash($password, PASSWORD_ARGON2ID);
$kdf_salt = random_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES);
$stmt = $pdo->prepare("INSERT INTO users (username, password_hash, kdf_salt, user_type) VALUES (?, ?, ?, 'admin')");
$stmt->execute([$username, $password_hash, $kdf_salt]);
$_SESSION['user_id'] = $pdo->lastInsertId();
$_SESSION['username'] = $username;
$_SESSION['user_type'] = 'admin';
$_SESSION['last_activity'] = time();
$mek = sodium_crypto_pwhash(SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES, $password, $kdf_salt, KDF_OPS_LIMIT, KDF_MEM_LIMIT);
$_SESSION['mek'] = $mek;
sodium_memzero($password);
sodium_memzero($mek);
echo json_encode(['success' => true]);
} catch (Exception $e) {
error_log("Setup Error: " . $e->getMessage()); // Log the actual error
if (file_exists(DB_PATH)) unlink(DB_PATH);
http_response_code(500);
echo json_encode(['error' => 'An error occurred during setup.']);
}
'''
login_handler_php_content = r''' 'Username and password are required.']); exit; }
try {
$pdo = get_db_connection();
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['user_type'] = $user['user_type'];
$_SESSION['last_activity'] = time();
$mek = sodium_crypto_pwhash(SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES, $password, $user['kdf_salt'], KDF_OPS_LIMIT, KDF_MEM_LIMIT);
$_SESSION['mek'] = $mek;
sodium_memzero($password);
sodium_memzero($mek);
echo json_encode(['success' => true]);
} else {
usleep(500000);
http_response_code(401);
echo json_encode(['error' => 'Wrong username or password.']);
}
} catch (Exception $e) {
error_log("Login Error: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'An internal server error occurred.']);
}
'''
logout_handler_php_content = r''' 'Authentication required.']); exit; }
$pdo = get_db_connection();
$user_id = $_SESSION['user_id'];
$mek = $_SESSION['mek'];
function encrypt_data($plaintext, $mek) {
$nonce = random_bytes(SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES);
$ciphertext = sodium_crypto_aead_aes256gcm_encrypt(json_encode($plaintext), '', $nonce, $mek);
return json_encode(['nonce' => base64_encode($nonce), 'ciphertext' => base64_encode($ciphertext)]);
}
function decrypt_data($encrypted_blob, $mek) {
$data = json_decode($encrypted_blob, true);
if (!isset($data['nonce'], $data['ciphertext'])) return null;
$nonce = base64_decode($data['nonce']);
$ciphertext = base64_decode($data['ciphertext']);
$decrypted = sodium_crypto_aead_aes256gcm_decrypt($ciphertext, '', $nonce, $mek);
return $decrypted ? json_decode($decrypted, true) : null;
}
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
$stmt = $pdo->prepare("SELECT * FROM accounts WHERE user_id = ?");
$stmt->execute([$user_id]);
$encrypted_accounts = $stmt->fetchAll();
$decrypted_accounts = [];
foreach ($encrypted_accounts as $enc_acc) {
if ($decrypted_data = decrypt_data($enc_acc['encrypted_blob'], $mek)) {
$decrypted_data['id'] = $enc_acc['id'];
$decrypted_accounts[] = $decrypted_data;
}
}
$categories = $pdo->query("SELECT * FROM categories ORDER BY name")->fetchAll();
$platforms = $pdo->query("SELECT * FROM platforms ORDER BY name")->fetchAll();
echo json_encode(['accounts' => $decrypted_accounts, 'categories' => $categories, 'platforms' => $platforms]);
break;
case 'POST':
$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? null;
unset($input['id']);
$encrypted_blob = encrypt_data($input, $mek);
if ($id) {
$stmt = $pdo->prepare("UPDATE accounts SET encrypted_blob = ? WHERE id = ? AND user_id = ?");
$stmt->execute([$encrypted_blob, $id, $user_id]);
} else {
$stmt = $pdo->prepare("INSERT INTO accounts (user_id, encrypted_blob) VALUES (?, ?)");
$stmt->execute([$user_id, $encrypted_blob]);
$id = $pdo->lastInsertId();
}
echo json_encode(['success' => true, 'id' => $id]);
break;
case 'DELETE':
$input = json_decode(file_get_contents('php://input'), true);
if (!($id = $input['id'] ?? null)) { http_response_code(400); exit; }
$stmt = $pdo->prepare("DELETE FROM accounts WHERE id = ? AND user_id = ?");
$stmt->execute([$id, $user_id]);
echo json_encode(['success' => $stmt->rowCount() > 0]);
break;
default:
http_response_code(405);
break;
}
sodium_memzero($mek);
'''
user_handler_php_content = r'''query("SELECT id, username, user_type FROM users")->fetchAll());
break;
case 'POST':
$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? null;
$username = trim($input['username'] ?? '');
$password = $input['password'] ?? '';
$user_type = $input['user_type'] ?? 'user';
if (empty($username) || !in_array($user_type, ['user', 'admin'])) { http_response_code(400); exit; }
if ($id) {
if (!empty($password)) {
$stmt = $pdo->prepare("UPDATE users SET username = ?, password_hash = ?, user_type = ? WHERE id = ?");
$stmt->execute([$username, password_hash($password, PASSWORD_ARGON2ID), $user_type, $id]);
} else {
$stmt = $pdo->prepare("UPDATE users SET username = ?, user_type = ? WHERE id = ?");
$stmt->execute([$username, $user_type, $id]);
}
} else {
if (empty($password) || strlen($password) < 8) { http_response_code(400); exit; }
$kdf_salt = random_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES);
$stmt = $pdo->prepare("INSERT INTO users (username, password_hash, kdf_salt, user_type) VALUES (?, ?, ?, ?)");
$stmt->execute([$username, password_hash($password, PASSWORD_ARGON2ID), $kdf_salt, $user_type]);
}
echo json_encode(['success' => true]);
break;
case 'DELETE':
$input = json_decode(file_get_contents('php://input'), true);
if (!($id = $input['id'] ?? null) || $id == $_SESSION['user_id']) { http_response_code(400); exit; }
$stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$id]);
echo json_encode(['success' => $stmt->rowCount() > 0]);
break;
default: http_response_code(405); break;
}
} catch (Exception $e) {
error_log("User Handler Error: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'An internal server error occurred.']);
}
'''
data_handler_php_content = r''' 'Authentication required.']); exit; }
$pdo = get_db_connection();
$user_id = $_SESSION['user_id'];
$mek = $_SESSION['mek'];
// ... (encryption/decryption functions remain the same) ...
function encrypt_data($plaintext, $mek) {
$nonce = random_bytes(SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES);
$ciphertext = sodium_crypto_aead_aes256gcm_encrypt(json_encode($plaintext), '', $nonce, $mek);
return json_encode(['nonce' => base64_encode($nonce), 'ciphertext' => base64_encode($ciphertext)]);
}
function decrypt_data($encrypted_blob, $mek) {
try {
$data = json_decode($encrypted_blob, true, 512, JSON_THROW_ON_ERROR);
if (!isset($data['nonce'], $data['ciphertext'])) return null;
$nonce = base64_decode($data['nonce'], true);
$ciphertext = base64_decode($data['ciphertext'], true);
if ($nonce === false || $ciphertext === false) return null;
$decrypted = sodium_crypto_aead_aes256gcm_decrypt($ciphertext, '', $nonce, $mek);
return $decrypted ? json_decode($decrypted, true, 512, JSON_THROW_ON_ERROR) : null;
} catch (Exception $e) {
error_log("Decryption failed for user_id {$user_id}: " . $e->getMessage());
return null; // Return null on any failure
}
}
try {
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
$stmt = $pdo->prepare("SELECT * FROM accounts WHERE user_id = ?");
$stmt->execute([$user_id]);
$encrypted_accounts = $stmt->fetchAll();
$decrypted_accounts = [];
foreach ($encrypted_accounts as $enc_acc) {
if ($decrypted_data = decrypt_data($enc_acc['encrypted_blob'], $mek)) {
$decrypted_data['id'] = $enc_acc['id'];
$decrypted_accounts[] = $decrypted_data;
}
}
$categories = $pdo->query("SELECT * FROM categories ORDER BY name")->fetchAll();
$platforms = $pdo->query("SELECT * FROM platforms ORDER BY name")->fetchAll();
echo json_encode(['accounts' => $decrypted_accounts, 'categories' => $categories, 'platforms' => $platforms]);
break;
case 'POST':
$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? null;
unset($input['id']);
$encrypted_blob = encrypt_data($input, $mek);
if ($id) {
$stmt = $pdo->prepare("UPDATE accounts SET encrypted_blob = ? WHERE id = ? AND user_id = ?");
$stmt->execute([$encrypted_blob, $id, $user_id]);
} else {
$stmt = $pdo->prepare("INSERT INTO accounts (user_id, encrypted_blob) VALUES (?, ?)");
$stmt->execute([$user_id, $encrypted_blob]);
$id = $pdo->lastInsertId();
}
echo json_encode(['success' => true, 'id' => $id]);
break;
case 'DELETE':
$input = json_decode(file_get_contents('php://input'), true);
if (!($id = $input['id'] ?? null)) { http_response_code(400); exit; }
$stmt = $pdo->prepare("DELETE FROM accounts WHERE id = ? AND user_id = ?");
$stmt->execute([$id, $user_id]);
echo json_encode(['success' => $stmt->rowCount() > 0]);
break;
default:
http_response_code(405);
break;
}
} catch(Exception $e) {
error_log("Data handler error: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'An internal server error occurred.']);
} finally {
if (isset($mek)) {
sodium_memzero($mek);
}
}
'''
password_change_handler_php_content = r''' 'All fields required; new password must be 8+ characters.']); exit; }
try {
$pdo = get_db_connection();
$stmt = $pdo->prepare("SELECT password_hash, kdf_salt FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch();
if (!$user || !password_verify($oldPassword, $user['password_hash'])) { http_response_code(401); echo json_encode(['error' => 'Incorrect old password.']); exit; }
$stmt = $pdo->prepare("UPDATE users SET password_hash = ? WHERE id = ?");
$stmt->execute([password_hash($newPassword, PASSWORD_ARGON2ID), $_SESSION['user_id']]);
$_SESSION['mek'] = sodium_crypto_pwhash(SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES, $newPassword, $user['kdf_salt'], KDF_OPS_LIMIT, KDF_MEM_LIMIT);
sodium_memzero($oldPassword); sodium_memzero($newPassword);
echo json_encode(['success' => true]);
} catch (Exception $e) {
error_log("Password Change Error: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'An internal server error occurred.']);
}
'''
recovery_handler_php_content = r'''prepare("SELECT password_hash FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$user = $stmt->fetch();
if (!$user || !password_verify($currentPassword, $user['password_hash'])) { http_response_code(401); exit; }
$recovery_code_formatted = implode('-', str_split(strtoupper(bin2hex(random_bytes(6))), 4));
$recovery_kdf_salt = random_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES);
$recovery_encryption_key = sodium_crypto_pwhash(SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES, $recovery_code_formatted, $recovery_kdf_salt, KDF_OPS_LIMIT, KDF_MEM_LIMIT);
$nonce = random_bytes(SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES);
$encrypted_mek = sodium_crypto_aead_aes256gcm_encrypt($_SESSION['mek'], '', $nonce, $recovery_encryption_key);
$recovery_blob = json_encode(['nonce' => base64_encode($nonce), 'ciphertext' => base64_encode($encrypted_mek)]);
$updateStmt = $pdo->prepare("UPDATE users SET recovery_encrypted_mek = ?, recovery_kdf_salt = ? WHERE id = ?");
$updateStmt->execute([$recovery_blob, $recovery_kdf_salt, $user_id]);
echo json_encode(['success' => true, 'recoveryCode' => $recovery_code_formatted]);
exit;
}
if ($action === 'verify_code') {
$username = $input['username'] ?? ''; $recoveryCode = $input['recoveryCode'] ?? '';
if (empty($username) || empty($recoveryCode)) { http_response_code(400); exit; }
$stmt = $pdo->prepare("SELECT id, recovery_encrypted_mek, recovery_kdf_salt FROM users WHERE username = ?");
$stmt->execute([$username]);
$user = $stmt->fetch();
if (!$user || empty($user['recovery_encrypted_mek'])) { http_response_code(401); exit; }
$key = sodium_crypto_pwhash(SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES, $recoveryCode, $user['recovery_kdf_salt'], KDF_OPS_LIMIT, KDF_MEM_LIMIT);
$blob = json_decode($user['recovery_encrypted_mek'], true);
if (sodium_crypto_aead_aes256gcm_decrypt(base64_decode($blob['ciphertext']), '', base64_decode($blob['nonce']), $key) === false) { http_response_code(401); exit; }
$reset_token = bin2hex(random_bytes(32));
$_SESSION['reset_token'] = $reset_token; $_SESSION['reset_user_id'] = $user['id']; $_SESSION['reset_expires'] = time() + 300;
echo json_encode(['success' => true, 'token' => $reset_token]);
exit;
}
if ($action === 'reset_password') {
$token = $input['token'] ?? ''; $newPassword = $input['newPassword'] ?? '';
if (empty($token) || empty($_SESSION['reset_token']) || !hash_equals($_SESSION['reset_token'], $token) || time() > $_SESSION['reset_expires']) { http_response_code(401); exit; }
if (strlen($newPassword) < 8) { http_response_code(400); exit; }
$stmt = $pdo->prepare("UPDATE users SET password_hash = ?, recovery_encrypted_mek = NULL, recovery_kdf_salt = NULL WHERE id = ?");
$stmt->execute([password_hash($newPassword, PASSWORD_ARGON2ID), $_SESSION['reset_user_id']]);
unset($_SESSION['reset_token'], $_SESSION['reset_user_id'], $_SESSION['reset_expires']);
echo json_encode(['success' => true]);
exit;
}
http_response_code(400);
} catch (Exception $e) {
error_log("Recovery Handler Error: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'An internal server error occurred.']);
}
'''
# --- PHP Page Files ---
index_php_content = r'''
'''
login_php_content = r'''
'''
dashboard_php_content = r'''
Account Credentials
Total Accounts: {{ accounts.length }}
| Search Results: {{ fullFilteredAccounts.length }}
{{ isEditMode ? 'Edit Account' : 'Add Account' }} Cancel {{ isEditMode ? 'Save' : 'Add' }}
Are you sure? {{ confirmMessage }}
Cancel Confirm
'''
user_php_content = r'''
User Management
Add User
Username User Type Actions
{{ user.username }}
{{ user.user_type }}
Edit
Delete
Are you sure? {{ confirmMessage }}
Cancel Confirm
'''
catagory_platform_php_content = r'''
Manage Categories & Platforms
Are you sure? {{ confirmMessage }}
Cancel Confirm
'''
change_password_php_content = r'''
'''
qrcode_php_content = r'''
Scan QR Code
Click to upload or drag and drop
You can also paste an image from your clipboard
Paste from Clipboard
Scan
Clear
'''
# --- Root index.php for redirection ---
root_index_php_content = r'''
Account Recovery
Recovery code accepted. Set a new password.
Verify Identity Enter current password to generate a new recovery code.
New Recovery Code
Store this in a safe place.
If you lose this and your password, data is lost forever.
Download Print
Done
Black-Clover - Account Recovery Code IMPORTANT: Store this code in a safe place. If you lose this and your password, your data will be permanently lost.
Your new recovery code is:
{{ generatedCode }}
'''
# ==============================================================================
# --- MAIN EXECUTION ---
# ==============================================================================
def main():
"""Main function to create the project structure."""
print("Starting Black-Clover project generation...")
os.makedirs(PROJECT_NAME, exist_ok=True)
create_file(os.path.join(PROJECT_NAME, "config/config.php"), config_php_content)
public_dirs = ["public/api", "public/includes"]
for d in public_dirs:
os.makedirs(os.path.join(PROJECT_NAME, d), exist_ok=True)
create_file(os.path.join(PROJECT_NAME, "public/index.php"), index_php_content)
create_file(os.path.join(PROJECT_NAME, "public/setup.php"), setup_php_content)
create_file(os.path.join(PROJECT_NAME, "public/login.php"), login_php_content)
create_file(os.path.join(PROJECT_NAME, "public/dashboard.php"), dashboard_php_content)
create_file(os.path.join(PROJECT_NAME, "public/user.php"), user_php_content)
create_file(os.path.join(PROJECT_NAME, "public/catagory-platform.php"), catagory_platform_php_content)
create_file(os.path.join(PROJECT_NAME, "public/change-password.php"), change_password_php_content)
create_file(os.path.join(PROJECT_NAME, "public/qrcode.php"), qrcode_php_content)
create_file(os.path.join(PROJECT_NAME, "public/recovery.php"), recovery_php_content)
create_file(os.path.join(PROJECT_NAME, "public/includes/bootstrap.php"), bootstrap_php_content)
create_file(os.path.join(PROJECT_NAME, "public/includes/header.php"), header_php_content)
create_file(os.path.join(PROJECT_NAME, "public/includes/footer.php"), footer_php_content)
create_file(os.path.join(PROJECT_NAME, "index.php"), root_index_php_content)
create_file(os.path.join(PROJECT_NAME, "public/api/setup_handler.php"), setup_handler_php_content)
create_file(os.path.join(PROJECT_NAME, "public/api/login_handler.php"), login_handler_php_content)
create_file(os.path.join(PROJECT_NAME, "public/api/logout_handler.php"), logout_handler_php_content)
create_file(os.path.join(PROJECT_NAME, "public/api/data_handler.php"), data_handler_php_content)
create_file(os.path.join(PROJECT_NAME, "public/api/user_handler.php"), user_handler_php_content)
create_file(os.path.join(PROJECT_NAME, "public/api/cp_handler.php"), cp_handler_php_content)
create_file(os.path.join(PROJECT_NAME, "public/api/password_change_handler.php"), password_change_handler_php_content)
create_file(os.path.join(PROJECT_NAME, "public/api/recovery_handler.php"), recovery_handler_php_content)
db_dir = os.path.join(PROJECT_NAME, "database")
os.makedirs(db_dir, exist_ok=True)
try:
os.chmod(db_dir, stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH)
print(f"Set permissions for directory: {db_dir}")
except Exception as e:
print(f"Warning: Could not set permissions for {db_dir}. Please ensure your web server can write to this directory. Error: {e}")
print("\nProject generation complete!")
print(f"Project created in: ./{PROJECT_NAME}/")
print("\nNext Steps:")
print(f"1. Delete any old '{PROJECT_NAME}' directory and its database.")
print(f"2. Point your local web server to the new '{PROJECT_NAME}/public' directory.")
print("3. Ensure local SSL (HTTPS) is enabled for your site.")
print("4. Open your site in a browser to begin the fresh setup.")
if __name__ == "__main__":
main()