mirror of
https://github.com/myronblair/epictravelexpeditions
synced 2026-06-30 17:50:08 -05:00
Initial commit
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
# Epic Travel & Expeditions - LiteSpeed .htaccess for CyberPanel
|
||||
# Optimized for LiteSpeed Web Server
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteBase /api/
|
||||
|
||||
# Route all requests to index.php
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)$ index.php/$1 [L,QSA]
|
||||
</IfModule>
|
||||
|
||||
# LiteSpeed Cache Control
|
||||
<IfModule LiteSpeed>
|
||||
# Disable caching for API
|
||||
CacheLookup off
|
||||
</IfModule>
|
||||
|
||||
# Security Headers
|
||||
<IfModule mod_headers.c>
|
||||
Header set X-Content-Type-Options "nosniff"
|
||||
Header set X-Frame-Options "SAMEORIGIN"
|
||||
Header set X-XSS-Protection "1; mode=block"
|
||||
Header set X-Powered-By "Epic Travel API"
|
||||
</IfModule>
|
||||
|
||||
# Protect sensitive files
|
||||
<FilesMatch "^(config\.php|\.env)">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
|
||||
# PHP Settings (LiteSpeed compatible)
|
||||
<IfModule mod_php7.c>
|
||||
php_value upload_max_filesize 10M
|
||||
php_value post_max_size 10M
|
||||
php_value max_execution_time 300
|
||||
php_value max_input_time 300
|
||||
php_value memory_limit 256M
|
||||
</IfModule>
|
||||
|
||||
# Compression
|
||||
<IfModule mod_deflate.c>
|
||||
AddOutputFilterByType DEFLATE application/json
|
||||
AddOutputFilterByType DEFLATE text/plain
|
||||
AddOutputFilterByType DEFLATE text/html
|
||||
</IfModule>
|
||||
|
||||
# Browser Caching
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive Off
|
||||
</IfModule>
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* Authentication Endpoints
|
||||
*/
|
||||
|
||||
$db = Database::getInstance()->getConnection();
|
||||
|
||||
// Login endpoint
|
||||
if ($method === 'POST' && $id === 'login') {
|
||||
$input = getJsonInput();
|
||||
|
||||
// Validate input
|
||||
$errors = validateRequired($input, ['email', 'password']);
|
||||
if (!empty($errors)) {
|
||||
jsonResponse(['error' => implode(', ', $errors)], 400);
|
||||
}
|
||||
|
||||
$email = sanitizeString($input['email']);
|
||||
$password = $input['password'];
|
||||
|
||||
// Find admin user
|
||||
$stmt = $db->prepare("SELECT * FROM admin_users WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
$admin = $stmt->fetch();
|
||||
|
||||
if (!$admin) {
|
||||
jsonResponse(['error' => 'Invalid email or password'], 401);
|
||||
}
|
||||
|
||||
// Verify password
|
||||
if (!password_verify($password, $admin['password_hash'])) {
|
||||
jsonResponse(['error' => 'Invalid email or password'], 401);
|
||||
}
|
||||
|
||||
// Create token
|
||||
$token = JWT::createToken($email);
|
||||
|
||||
jsonResponse([
|
||||
'access_token' => $token,
|
||||
'token_type' => 'bearer',
|
||||
'email' => $email
|
||||
]);
|
||||
}
|
||||
|
||||
// Verify token endpoint
|
||||
if ($method === 'POST' && $id === 'verify') {
|
||||
$payload = requireAuth();
|
||||
|
||||
jsonResponse([
|
||||
'valid' => true,
|
||||
'email' => $payload['sub']
|
||||
]);
|
||||
}
|
||||
|
||||
jsonResponse(['error' => 'Invalid auth endpoint'], 404);
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* Destination Categories Endpoints
|
||||
*/
|
||||
$db = Database::getInstance()->getConnection();
|
||||
|
||||
// GET all categories (public - needed for destination forms)
|
||||
if ($method === 'GET' && !$id) {
|
||||
$stmt = $db->query('SELECT id, name FROM destination_categories ORDER BY name');
|
||||
jsonResponse($stmt->fetchAll());
|
||||
}
|
||||
|
||||
// POST add category (admin)
|
||||
if ($method === 'POST') {
|
||||
requireAuth();
|
||||
$input = getJsonInput();
|
||||
$errors = validateRequired($input, ['name']);
|
||||
if (!empty($errors)) jsonResponse(['error' => implode(', ', $errors)], 400);
|
||||
$name = sanitizeString($input['name']);
|
||||
try {
|
||||
$stmt = $db->prepare('INSERT INTO destination_categories (name) VALUES (?)');
|
||||
$stmt->execute([$name]);
|
||||
$newId = $db->lastInsertId();
|
||||
jsonResponse(['id' => $newId, 'name' => $name], 201);
|
||||
} catch (Exception $e) {
|
||||
jsonResponse(['error' => 'Category already exists'], 409);
|
||||
}
|
||||
}
|
||||
|
||||
// PUT rename category (admin) - also updates all destinations using it
|
||||
if ($method === 'PUT' && $id) {
|
||||
requireAuth();
|
||||
$input = getJsonInput();
|
||||
$errors = validateRequired($input, ['name']);
|
||||
if (!empty($errors)) jsonResponse(['error' => implode(', ', $errors)], 400);
|
||||
$newName = sanitizeString($input['name']);
|
||||
$stmt = $db->prepare('SELECT name FROM destination_categories WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
$old = $stmt->fetch();
|
||||
if (!$old) jsonResponse(['error' => 'Category not found'], 404);
|
||||
try {
|
||||
$db->prepare('UPDATE destination_categories SET name = ? WHERE id = ?')->execute([$newName, $id]);
|
||||
$db->prepare('UPDATE destinations SET category = ? WHERE category = ?')->execute([$newName, $old['name']]);
|
||||
jsonResponse(['id' => $id, 'name' => $newName]);
|
||||
} catch (Exception $e) {
|
||||
jsonResponse(['error' => 'Category name already exists'], 409);
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE category (admin) - only if no destinations use it
|
||||
if ($method === 'DELETE' && $id) {
|
||||
requireAuth();
|
||||
$stmt = $db->prepare('SELECT name FROM destination_categories WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
$cat = $stmt->fetch();
|
||||
if (!$cat) jsonResponse(['error' => 'Category not found'], 404);
|
||||
$stmt = $db->prepare('SELECT COUNT(*) FROM destinations WHERE category = ?');
|
||||
$stmt->execute([$cat['name']]);
|
||||
if ($stmt->fetchColumn() > 0) jsonResponse(['error' => 'Cannot delete — destinations are using this category'], 400);
|
||||
$db->prepare('DELETE FROM destination_categories WHERE id = ?')->execute([$id]);
|
||||
jsonResponse(['message' => 'Category deleted']);
|
||||
}
|
||||
|
||||
jsonResponse(['error' => 'Invalid categories endpoint'], 404);
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Contact Form Endpoint
|
||||
*/
|
||||
|
||||
$db = Database::getInstance()->getConnection();
|
||||
|
||||
if ($method === 'POST') {
|
||||
$input = getJsonInput();
|
||||
|
||||
$errors = validateRequired($input, ['name', 'email', 'message']);
|
||||
if (!empty($errors)) {
|
||||
jsonResponse(['error' => implode(', ', $errors)], 400);
|
||||
}
|
||||
|
||||
if (!isValidEmail($input['email'])) {
|
||||
jsonResponse(['error' => 'Invalid email address'], 400);
|
||||
}
|
||||
|
||||
$name = sanitizeString($input['name']);
|
||||
$email = sanitizeString($input['email']);
|
||||
$message = $input['message'];
|
||||
$id = generateUuid();
|
||||
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO contacts (id, name, email, message, created_at)
|
||||
VALUES (?, ?, ?, ?, NOW())
|
||||
");
|
||||
$stmt->execute([$id, $name, $email, $message]);
|
||||
|
||||
// Send admin alert and customer confirmation via SendGrid
|
||||
sendContactAlert($name, $email, $message);
|
||||
sendContactConfirmation($email, $name);
|
||||
|
||||
jsonResponse(['message' => 'Contact form submitted successfully']);
|
||||
}
|
||||
|
||||
jsonResponse(['error' => 'Method not allowed'], 405);
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
/**
|
||||
* Destinations CRUD Endpoints
|
||||
*/
|
||||
|
||||
$db = Database::getInstance()->getConnection();
|
||||
|
||||
// GET all destinations or single destination
|
||||
if ($method === 'GET') {
|
||||
if ($id) {
|
||||
// Get single destination
|
||||
$stmt = $db->prepare("SELECT * FROM destinations WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$destination = $stmt->fetch();
|
||||
|
||||
if (!$destination) {
|
||||
jsonResponse(['error' => 'Destination not found'], 404);
|
||||
}
|
||||
|
||||
jsonResponse($destination);
|
||||
} else {
|
||||
// Get all destinations with optional filtering
|
||||
$category = isset($_GET['category']) ? sanitizeString($_GET['category']) : null;
|
||||
$search = isset($_GET['search']) ? sanitizeString($_GET['search']) : null;
|
||||
|
||||
$sql = "SELECT * FROM destinations WHERE 1=1";
|
||||
$params = [];
|
||||
|
||||
if ($category && $category !== 'All') {
|
||||
$sql .= " AND category = ?";
|
||||
$params[] = $category;
|
||||
}
|
||||
|
||||
if ($search) {
|
||||
$sql .= " AND (name LIKE ? OR location LIKE ?)";
|
||||
$params[] = "%$search%";
|
||||
$params[] = "%$search%";
|
||||
}
|
||||
|
||||
$sql .= " LIMIT 100";
|
||||
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$destinations = $stmt->fetchAll();
|
||||
|
||||
jsonResponse($destinations);
|
||||
}
|
||||
}
|
||||
|
||||
// POST create new destination (admin only)
|
||||
if ($method === 'POST') {
|
||||
requireAuth();
|
||||
|
||||
$input = getJsonInput();
|
||||
|
||||
$errors = validateRequired($input, ['name', 'location', 'description', 'image', 'category', 'rating', 'price']);
|
||||
if (!empty($errors)) {
|
||||
jsonResponse(['error' => implode(', ', $errors)], 400);
|
||||
}
|
||||
|
||||
$id = generateUuid();
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO destinations (id, name, location, description, image, category, rating, price, currency, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
$id,
|
||||
sanitizeString($input['name']),
|
||||
sanitizeString($input['location']),
|
||||
$input['description'],
|
||||
$input['image'],
|
||||
$input['category'],
|
||||
$input['rating'],
|
||||
$input['price'],
|
||||
isset($input['currency']) ? $input['currency'] : 'USD'
|
||||
]);
|
||||
|
||||
// Fetch created destination
|
||||
$stmt = $db->prepare("SELECT * FROM destinations WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$destination = $stmt->fetch();
|
||||
|
||||
jsonResponse($destination, 201);
|
||||
}
|
||||
|
||||
// PUT update destination (admin only)
|
||||
if ($method === 'PUT' && $id) {
|
||||
requireAuth();
|
||||
|
||||
$input = getJsonInput();
|
||||
|
||||
// Build update query dynamically
|
||||
$updates = [];
|
||||
$params = [];
|
||||
|
||||
$allowedFields = ['name', 'location', 'description', 'image', 'category', 'rating', 'price', 'currency'];
|
||||
|
||||
foreach ($allowedFields as $field) {
|
||||
if (isset($input[$field])) {
|
||||
$updates[] = "$field = ?";
|
||||
$params[] = $field === 'description' ? $input[$field] : sanitizeString($input[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($updates)) {
|
||||
jsonResponse(['error' => 'No fields to update'], 400);
|
||||
}
|
||||
|
||||
$params[] = $id;
|
||||
|
||||
$sql = "UPDATE destinations SET " . implode(', ', $updates) . " WHERE id = ?";
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
// Fetch updated destination
|
||||
$stmt = $db->prepare("SELECT * FROM destinations WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$destination = $stmt->fetch();
|
||||
|
||||
jsonResponse($destination);
|
||||
}
|
||||
|
||||
// DELETE destination (admin only)
|
||||
if ($method === 'DELETE' && $id) {
|
||||
requireAuth();
|
||||
|
||||
// Delete destination (cascades to specials)
|
||||
$stmt = $db->prepare("DELETE FROM destinations WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
if ($stmt->rowCount() === 0) {
|
||||
jsonResponse(['error' => 'Destination not found'], 404);
|
||||
}
|
||||
|
||||
jsonResponse(['message' => 'Destination deleted successfully']);
|
||||
}
|
||||
|
||||
jsonResponse(['error' => 'Invalid destinations endpoint'], 404);
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* Newsletter Subscription Endpoint
|
||||
*/
|
||||
|
||||
$db = Database::getInstance()->getConnection();
|
||||
|
||||
if ($method === 'POST' && $id === 'subscribe') {
|
||||
$input = getJsonInput();
|
||||
|
||||
if (!isset($input['email']) || !isValidEmail($input['email'])) {
|
||||
jsonResponse(['error' => 'Valid email address is required'], 400);
|
||||
}
|
||||
|
||||
$email = sanitizeString($input['email']);
|
||||
|
||||
// Check if already subscribed
|
||||
$stmt = $db->prepare("SELECT id FROM newsletter_subscribers WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
|
||||
if ($stmt->fetch()) {
|
||||
jsonResponse(['message' => 'Email already subscribed']);
|
||||
}
|
||||
|
||||
$id = generateUuid();
|
||||
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO newsletter_subscribers (id, email, subscribed_at)
|
||||
VALUES (?, ?, NOW())
|
||||
");
|
||||
|
||||
$stmt->execute([$id, $email]);
|
||||
|
||||
jsonResponse(['message' => 'Successfully subscribed to newsletter']);
|
||||
}
|
||||
|
||||
jsonResponse(['error' => 'Invalid newsletter endpoint'], 404);
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
/**
|
||||
* Weekly Specials CRUD Endpoints
|
||||
*/
|
||||
|
||||
$db = Database::getInstance()->getConnection();
|
||||
|
||||
// GET all specials
|
||||
if ($method === 'GET' && !$id) {
|
||||
$stmt = $db->query("SELECT * FROM specials LIMIT 100");
|
||||
$specials = $stmt->fetchAll();
|
||||
|
||||
// Parse JSON highlights
|
||||
foreach ($specials as &$special) {
|
||||
$special['highlights'] = json_decode($special['highlights'], true);
|
||||
}
|
||||
|
||||
jsonResponse($specials);
|
||||
}
|
||||
|
||||
// POST create special (admin only)
|
||||
if ($method === 'POST') {
|
||||
requireAuth();
|
||||
|
||||
$input = getJsonInput();
|
||||
|
||||
$errors = validateRequired($input, ['destination_id', 'discount', 'end_date', 'highlights']);
|
||||
if (!empty($errors)) {
|
||||
jsonResponse(['error' => implode(', ', $errors)], 400);
|
||||
}
|
||||
|
||||
// Check if destination exists
|
||||
$stmt = $db->prepare("SELECT id FROM destinations WHERE id = ?");
|
||||
$stmt->execute([$input['destination_id']]);
|
||||
if (!$stmt->fetch()) {
|
||||
jsonResponse(['error' => 'Destination not found'], 404);
|
||||
}
|
||||
|
||||
// Check if special already exists for this destination
|
||||
$stmt = $db->prepare("SELECT id FROM specials WHERE destination_id = ?");
|
||||
$stmt->execute([$input['destination_id']]);
|
||||
if ($stmt->fetch()) {
|
||||
jsonResponse(['error' => 'Special already exists for this destination'], 400);
|
||||
}
|
||||
|
||||
$id = generateUuid();
|
||||
$highlights = json_encode($input['highlights']);
|
||||
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO specials (id, destination_id, discount, end_date, highlights, image_path, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, NOW())
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
$id,
|
||||
$input['destination_id'],
|
||||
$input['discount'],
|
||||
$input['end_date'],
|
||||
$highlights,
|
||||
isset($input['image_path']) ? $input['image_path'] : null
|
||||
]);
|
||||
|
||||
// Fetch created special
|
||||
$stmt = $db->prepare("SELECT * FROM specials WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$special = $stmt->fetch();
|
||||
$special['highlights'] = json_decode($special['highlights'], true);
|
||||
|
||||
jsonResponse($special, 201);
|
||||
}
|
||||
|
||||
// PUT update special (admin only)
|
||||
if ($method === 'PUT' && $id) {
|
||||
requireAuth();
|
||||
|
||||
$input = getJsonInput();
|
||||
|
||||
$updates = [];
|
||||
$params = [];
|
||||
|
||||
if (isset($input['discount'])) {
|
||||
$updates[] = "discount = ?";
|
||||
$params[] = $input['discount'];
|
||||
}
|
||||
|
||||
if (isset($input['end_date'])) {
|
||||
$updates[] = "end_date = ?";
|
||||
$params[] = $input['end_date'];
|
||||
}
|
||||
|
||||
if (isset($input['highlights'])) {
|
||||
$updates[] = "highlights = ?";
|
||||
$params[] = json_encode($input['highlights']);
|
||||
}
|
||||
|
||||
if (empty($updates)) {
|
||||
jsonResponse(['error' => 'No fields to update'], 400);
|
||||
}
|
||||
|
||||
$params[] = $id;
|
||||
|
||||
$sql = "UPDATE specials SET " . implode(', ', $updates) . " WHERE id = ?";
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
// Fetch updated special
|
||||
$stmt = $db->prepare("SELECT * FROM specials WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$special = $stmt->fetch();
|
||||
$special['highlights'] = json_decode($special['highlights'], true);
|
||||
|
||||
jsonResponse($special);
|
||||
}
|
||||
|
||||
// DELETE special by destination_id (admin only)
|
||||
if ($method === 'DELETE' && isset($pathParts[1]) && $pathParts[1] === 'destination' && isset($pathParts[2])) {
|
||||
requireAuth();
|
||||
|
||||
$destinationId = $pathParts[2];
|
||||
|
||||
$stmt = $db->prepare("DELETE FROM specials WHERE destination_id = ?");
|
||||
$stmt->execute([$destinationId]);
|
||||
|
||||
if ($stmt->rowCount() === 0) {
|
||||
jsonResponse(['error' => 'Special not found for this destination'], 404);
|
||||
}
|
||||
|
||||
jsonResponse(['message' => 'Special removed successfully']);
|
||||
}
|
||||
|
||||
jsonResponse(['error' => 'Invalid specials endpoint'], 404);
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* Testimonials Endpoints
|
||||
*/
|
||||
|
||||
$db = Database::getInstance()->getConnection();
|
||||
|
||||
// GET approved testimonials (public)
|
||||
if ($method === 'GET' && !$id) {
|
||||
$stmt = $db->query("SELECT id, full_name, location, message, image_path, created_at FROM testimonials WHERE status = 'approved' ORDER BY created_at DESC");
|
||||
jsonResponse($stmt->fetchAll());
|
||||
}
|
||||
|
||||
// GET all testimonials (admin)
|
||||
if ($method === 'GET' && $id === 'all') {
|
||||
requireAuth();
|
||||
$stmt = $db->query("SELECT * FROM testimonials ORDER BY created_at DESC");
|
||||
jsonResponse($stmt->fetchAll());
|
||||
}
|
||||
|
||||
// POST submit testimonial (public)
|
||||
if ($method === 'POST' && !$id) {
|
||||
$input = getJsonInput();
|
||||
$errors = validateRequired($input, ['full_name', 'location', 'message']);
|
||||
if (!empty($errors)) {
|
||||
jsonResponse(['error' => implode(', ', $errors)], 400);
|
||||
}
|
||||
|
||||
$testimonialId = generateUuid();
|
||||
$stmt = $db->prepare("INSERT INTO testimonials (id, full_name, location, message, image_path, status, created_at) VALUES (?, ?, ?, ?, ?, 'pending', NOW())");
|
||||
$stmt->execute([
|
||||
$testimonialId,
|
||||
sanitizeString($input['full_name']),
|
||||
sanitizeString($input['location']),
|
||||
sanitizeString($input['message']),
|
||||
isset($input['image_path']) ? $input['image_path'] : null
|
||||
]);
|
||||
jsonResponse(['message' => 'Testimonial submitted successfully', 'id' => $testimonialId], 201);
|
||||
}
|
||||
|
||||
// PUT approve/deny/edit testimonial (admin)
|
||||
if ($method === 'PUT' && $id) {
|
||||
requireAuth();
|
||||
$input = getJsonInput();
|
||||
$updates = [];
|
||||
$params = [];
|
||||
|
||||
if (isset($input['status']) && in_array($input['status'], ['pending', 'approved', 'denied'])) {
|
||||
$updates[] = 'status = ?';
|
||||
$params[] = $input['status'];
|
||||
}
|
||||
if (isset($input['full_name'])) { $updates[] = 'full_name = ?'; $params[] = sanitizeString($input['full_name']); }
|
||||
if (isset($input['location'])) { $updates[] = 'location = ?'; $params[] = sanitizeString($input['location']); }
|
||||
if (isset($input['message'])) { $updates[] = 'message = ?'; $params[] = sanitizeString($input['message']); }
|
||||
|
||||
if (empty($updates)) jsonResponse(['error' => 'No fields to update'], 400);
|
||||
|
||||
$params[] = $id;
|
||||
$stmt = $db->prepare('UPDATE testimonials SET ' . implode(', ', $updates) . ' WHERE id = ?');
|
||||
$stmt->execute($params);
|
||||
|
||||
$stmt = $db->prepare('SELECT * FROM testimonials WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
jsonResponse($stmt->fetch());
|
||||
}
|
||||
|
||||
// DELETE testimonial (admin)
|
||||
if ($method === 'DELETE' && $id) {
|
||||
requireAuth();
|
||||
$stmt = $db->prepare('DELETE FROM testimonials WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
if ($stmt->rowCount() === 0) jsonResponse(['error' => 'Testimonial not found'], 404);
|
||||
jsonResponse(['message' => 'Testimonial deleted']);
|
||||
}
|
||||
|
||||
|
||||
// POST upload testimonial image (public)
|
||||
if ($method === "POST" && $id === "upload") {
|
||||
if (!isset($_FILES["file"])) jsonResponse(["error" => "No file uploaded"], 400);
|
||||
$file = $_FILES["file"];
|
||||
if ($file["error"] !== UPLOAD_ERR_OK) jsonResponse(["error" => "Upload error"], 400);
|
||||
if ($file["size"] > MAX_UPLOAD_SIZE) jsonResponse(["error" => "File too large (max 5MB)"], 400);
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mime = finfo_file($finfo, $file["tmp_name"]);
|
||||
finfo_close($finfo);
|
||||
$allowed = ["image/jpeg","image/jpg","image/png","image/webp"];
|
||||
if (!in_array($mime, $allowed)) jsonResponse(["error" => "Invalid file type"], 400);
|
||||
$ext = strtolower(pathinfo($file["name"], PATHINFO_EXTENSION));
|
||||
$name = generateUuid() . "." . $ext;
|
||||
$dest = UPLOAD_DIR . $name;
|
||||
if (!move_uploaded_file($file["tmp_name"], $dest)) jsonResponse(["error" => "Failed to save"], 500);
|
||||
jsonResponse(["url" => "/api/uploads/" . $name]);
|
||||
}
|
||||
|
||||
jsonResponse(['error' => 'Invalid testimonials endpoint'], 404);
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* Image Upload Endpoint
|
||||
*/
|
||||
|
||||
requireAuth(); // Only authenticated users can upload
|
||||
|
||||
if ($method === 'POST' && $id === 'image') {
|
||||
if (!isset($_FILES['file'])) {
|
||||
jsonResponse(['error' => 'No file uploaded'], 400);
|
||||
}
|
||||
|
||||
$file = $_FILES['file'];
|
||||
|
||||
// Validate file
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
jsonResponse(['error' => 'File upload failed'], 400);
|
||||
}
|
||||
|
||||
// Check file size
|
||||
if ($file['size'] > MAX_UPLOAD_SIZE) {
|
||||
jsonResponse(['error' => 'File too large. Maximum size is 5MB'], 400);
|
||||
}
|
||||
|
||||
// Check file type
|
||||
$allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp'];
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mimeType = finfo_file($finfo, $file['tmp_name']);
|
||||
finfo_close($finfo);
|
||||
|
||||
if (!in_array($mimeType, $allowedTypes)) {
|
||||
jsonResponse(['error' => 'Invalid file type. Only JPG, PNG, and WebP allowed'], 400);
|
||||
}
|
||||
|
||||
// Generate unique filename
|
||||
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
|
||||
$filename = generateUuid() . '.' . $extension;
|
||||
$filepath = UPLOAD_DIR . $filename;
|
||||
|
||||
// Move uploaded file
|
||||
if (!move_uploaded_file($file['tmp_name'], $filepath)) {
|
||||
jsonResponse(['error' => 'Failed to save file'], 500);
|
||||
}
|
||||
|
||||
$fileUrl = '/api/uploads/' . $filename;
|
||||
|
||||
jsonResponse([
|
||||
'url' => $fileUrl,
|
||||
'filename' => $filename
|
||||
]);
|
||||
}
|
||||
|
||||
// Serve uploaded images
|
||||
if ($method === 'GET' && isset($pathParts[1]) && $pathParts[1] === 'uploads' && isset($pathParts[2])) {
|
||||
$filename = basename($pathParts[2]);
|
||||
$filepath = UPLOAD_DIR . $filename;
|
||||
|
||||
if (!file_exists($filepath)) {
|
||||
jsonResponse(['error' => 'Image not found'], 404);
|
||||
}
|
||||
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mimeType = finfo_file($finfo, $filepath);
|
||||
finfo_close($finfo);
|
||||
|
||||
header('Content-Type: ' . $mimeType);
|
||||
header('Content-Length: ' . filesize($filepath));
|
||||
readfile($filepath);
|
||||
exit;
|
||||
}
|
||||
|
||||
jsonResponse(['error' => 'Invalid upload endpoint'], 404);
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* Database Connection Class
|
||||
* Uses PDO for secure MySQL connections
|
||||
*/
|
||||
|
||||
class Database {
|
||||
private static $instance = null;
|
||||
private $conn;
|
||||
|
||||
private function __construct() {
|
||||
try {
|
||||
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
|
||||
$this->conn = new PDO($dsn, DB_USER, DB_PASS, $options);
|
||||
} catch (PDOException $e) {
|
||||
$this->handleError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static function getInstance() {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function getConnection() {
|
||||
return $this->conn;
|
||||
}
|
||||
|
||||
private function handleError($message) {
|
||||
if (DEBUG_MODE) {
|
||||
die(json_encode(['error' => 'Database Error: ' . $message]));
|
||||
} else {
|
||||
die(json_encode(['error' => 'Database connection failed']));
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent cloning
|
||||
private function __clone() {}
|
||||
|
||||
// Prevent unserialization
|
||||
public function __wakeup() {
|
||||
throw new Exception("Cannot unserialize singleton");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* Helper Functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set CORS headers
|
||||
*/
|
||||
function setCorsHeaders() {
|
||||
$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';
|
||||
|
||||
if ($origin && (ALLOWED_ORIGINS === '*' || strpos(ALLOWED_ORIGINS, $origin) !== false)) {
|
||||
header("Access-Control-Allow-Origin: $origin");
|
||||
} else {
|
||||
header("Access-Control-Allow-Origin: " . ALLOWED_ORIGINS);
|
||||
}
|
||||
|
||||
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
|
||||
header("Access-Control-Allow-Headers: Content-Type, Authorization");
|
||||
header("Access-Control-Allow-Credentials: true");
|
||||
|
||||
// Handle preflight requests
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send JSON response
|
||||
*/
|
||||
function jsonResponse($data, $statusCode = 200) {
|
||||
http_response_code($statusCode);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JSON input
|
||||
*/
|
||||
function getJsonInput() {
|
||||
$input = file_get_contents('php://input');
|
||||
return json_decode($input, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate email
|
||||
*/
|
||||
function isValidEmail($email) {
|
||||
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate UUID v4
|
||||
*/
|
||||
function generateUuid() {
|
||||
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize string
|
||||
*/
|
||||
function sanitizeString($string) {
|
||||
return htmlspecialchars(strip_tags(trim($string)), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate required fields
|
||||
*/
|
||||
function validateRequired($data, $requiredFields) {
|
||||
$errors = [];
|
||||
|
||||
foreach ($requiredFields as $field) {
|
||||
if (!isset($data[$field]) || empty(trim($data[$field]))) {
|
||||
$errors[] = "$field is required";
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* JWT Authentication Functions
|
||||
* Simple JWT implementation for PHP
|
||||
*/
|
||||
|
||||
class JWT {
|
||||
|
||||
/**
|
||||
* Create a JWT token
|
||||
*/
|
||||
public static function encode($payload, $secret) {
|
||||
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
|
||||
$payload = json_encode($payload);
|
||||
|
||||
$base64UrlHeader = self::base64UrlEncode($header);
|
||||
$base64UrlPayload = self::base64UrlEncode($payload);
|
||||
|
||||
$signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $secret, true);
|
||||
$base64UrlSignature = self::base64UrlEncode($signature);
|
||||
|
||||
return $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode and verify a JWT token
|
||||
*/
|
||||
public static function decode($jwt, $secret) {
|
||||
$parts = explode('.', $jwt);
|
||||
|
||||
if (count($parts) !== 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($base64UrlHeader, $base64UrlPayload, $base64UrlSignature) = $parts;
|
||||
|
||||
$signature = self::base64UrlDecode($base64UrlSignature);
|
||||
$expectedSignature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $secret, true);
|
||||
|
||||
if (!hash_equals($signature, $expectedSignature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$payload = json_decode(self::base64UrlDecode($base64UrlPayload), true);
|
||||
|
||||
// Check expiration
|
||||
if (isset($payload['exp']) && $payload['exp'] < time()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create authentication token
|
||||
*/
|
||||
public static function createToken($email) {
|
||||
$payload = [
|
||||
'sub' => $email,
|
||||
'iat' => time(),
|
||||
'exp' => time() + JWT_EXPIRY
|
||||
];
|
||||
|
||||
return self::encode($payload, JWT_SECRET_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify token from Authorization header
|
||||
*/
|
||||
public static function verifyToken() {
|
||||
$headers = getallheaders();
|
||||
|
||||
if (!isset($headers['Authorization'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$authHeader = $headers['Authorization'];
|
||||
|
||||
if (!preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$token = $matches[1];
|
||||
$payload = self::decode($token, JWT_SECRET_KEY);
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 URL encode
|
||||
*/
|
||||
private static function base64UrlEncode($data) {
|
||||
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 URL decode
|
||||
*/
|
||||
private static function base64UrlDecode($data) {
|
||||
return base64_decode(str_replace(['-', '_'], ['+', '/'], $data));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Require authentication middleware
|
||||
*/
|
||||
function requireAuth() {
|
||||
$payload = JWT::verifyToken();
|
||||
|
||||
if (!$payload) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/**
|
||||
* Epic Travel Expeditions — SendGrid Mailer
|
||||
*/
|
||||
|
||||
function sendgridSend(string $toEmail, string $toName, string $subject, string $htmlBody, string $textBody = ''): bool {
|
||||
$apiKey = defined('SENDGRID_API_KEY') ? SENDGRID_API_KEY : '';
|
||||
if (!$apiKey || strpos($apiKey, 'YOUR_KEY') !== false) {
|
||||
error_log('[EpicTravel mailer] SENDGRID_API_KEY not configured');
|
||||
return false;
|
||||
}
|
||||
|
||||
$payload = json_encode([
|
||||
'personalizations' => [['to' => [['email' => $toEmail, 'name' => $toName]]]],
|
||||
'from' => [
|
||||
'email' => defined('MAIL_FROM') ? MAIL_FROM : 'noreply@epictravelexpeditions.com',
|
||||
'name' => defined('MAIL_FROM_NAME') ? MAIL_FROM_NAME : 'Epic Travel Expeditions',
|
||||
],
|
||||
'subject' => $subject,
|
||||
'content' => array_values(array_filter([
|
||||
$textBody ? ['type' => 'text/plain', 'value' => $textBody] : null,
|
||||
['type' => 'text/html', 'value' => $htmlBody],
|
||||
])),
|
||||
]);
|
||||
|
||||
$ch = curl_init('https://api.sendgrid.com/v3/mail/send');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $apiKey,
|
||||
'Content-Type: application/json',
|
||||
],
|
||||
CURLOPT_TIMEOUT => 20,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode === 202) return true;
|
||||
error_log('[EpicTravel mailer] SendGrid HTTP ' . $httpCode . ' — ' . $response);
|
||||
return false;
|
||||
}
|
||||
|
||||
function sendContactAlert(string $name, string $email, string $message): bool {
|
||||
$adminEmail = defined('ADMIN_EMAIL') ? ADMIN_EMAIL : 'admin@epictravelexpeditions.com';
|
||||
$subject = "New Contact Form Submission from {$name}";
|
||||
$html = '
|
||||
<div style="max-width:600px;margin:0 auto;font-family:Arial,sans-serif;">
|
||||
<div style="background:#1a3a6b;padding:24px;text-align:center;">
|
||||
<h1 style="color:#fff;margin:0;font-size:22px;">Epic Travel Expeditions</h1>
|
||||
<p style="color:rgba(255,255,255,.7);margin:4px 0 0;font-size:14px;">New Contact Form Message</p>
|
||||
</div>
|
||||
<div style="padding:28px;background:#fff;border:1px solid #e5e7eb;">
|
||||
<table style="width:100%;border-collapse:collapse;">
|
||||
<tr><td style="padding:8px 0;color:#6b7280;font-size:13px;width:80px;">Name</td>
|
||||
<td style="padding:8px 0;font-weight:600;">' . htmlspecialchars($name) . '</td></tr>
|
||||
<tr><td style="padding:8px 0;color:#6b7280;font-size:13px;">Email</td>
|
||||
<td style="padding:8px 0;"><a href="mailto:' . htmlspecialchars($email) . '" style="color:#1a3a6b;">' . htmlspecialchars($email) . '</a></td></tr>
|
||||
</table>
|
||||
<div style="margin-top:20px;padding:16px;background:#f9fafb;border-radius:8px;border-left:4px solid #1a3a6b;">
|
||||
<p style="margin:0;font-size:14px;color:#374151;line-height:1.6;">' . nl2br(htmlspecialchars($message)) . '</p>
|
||||
</div>
|
||||
<p style="margin-top:20px;font-size:13px;color:#9ca3af;">Submitted ' . date('F j, Y \a\t g:i A T') . '</p>
|
||||
</div>
|
||||
<div style="background:#f3f4f6;padding:16px;text-align:center;">
|
||||
<p style="margin:0;font-size:12px;color:#9ca3af;">© ' . date('Y') . ' Epic Travel Expeditions</p>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
return sendgridSend($adminEmail, 'Epic Travel Admin', $subject, $html,
|
||||
"New contact from {$name} ({$email}):\n\n{$message}");
|
||||
}
|
||||
|
||||
function sendContactConfirmation(string $toEmail, string $toName): bool {
|
||||
$subject = "We received your message — Epic Travel Expeditions";
|
||||
$html = '
|
||||
<div style="max-width:600px;margin:0 auto;font-family:Arial,sans-serif;">
|
||||
<div style="background:#1a3a6b;padding:24px;text-align:center;">
|
||||
<h1 style="color:#fff;margin:0;font-size:22px;">Epic Travel Expeditions</h1>
|
||||
</div>
|
||||
<div style="padding:32px;background:#fff;">
|
||||
<h2 style="margin-top:0;color:#1a3a6b;">Thanks for reaching out, ' . htmlspecialchars($toName) . '!</h2>
|
||||
<p style="color:#374151;line-height:1.6;">We received your message and our team will get back to you within 1–2 business days.</p>
|
||||
<p style="color:#374151;line-height:1.6;">In the meantime, feel free to browse our destinations and current travel specials:</p>
|
||||
<div style="text-align:center;margin:28px 0;">
|
||||
<a href="https://epictravelexpeditions.com" style="display:inline-block;background:#1a3a6b;color:#fff;padding:14px 28px;border-radius:8px;text-decoration:none;font-weight:bold;">Explore Destinations</a>
|
||||
</div>
|
||||
<p style="color:#374151;line-height:1.6;">Adventure awaits,<br><strong>The Epic Travel Team</strong></p>
|
||||
</div>
|
||||
<div style="background:#f3f4f6;padding:16px;text-align:center;">
|
||||
<p style="margin:0;font-size:12px;color:#9ca3af;">© ' . date('Y') . ' Epic Travel Expeditions</p>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
return sendgridSend($toEmail, $toName, $subject, $html,
|
||||
"Hi {$toName},\n\nThanks for contacting Epic Travel Expeditions! We'll get back to you within 1-2 business days.\n\nAdventure awaits,\nThe Epic Travel Team");
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* Epic Travel & Expeditions - Main API Entry Point
|
||||
* This file routes all API requests to appropriate handlers
|
||||
*/
|
||||
|
||||
// Load configuration and dependencies
|
||||
require_once __DIR__ . '/config.php';
|
||||
require_once __DIR__ . '/includes/database.php';
|
||||
require_once __DIR__ . '/includes/jwt.php';
|
||||
require_once __DIR__ . '/includes/functions.php';
|
||||
require_once __DIR__ . '/includes/mailer.php';
|
||||
|
||||
// Set CORS headers (handles OPTIONS preflight too)
|
||||
setCorsHeaders();
|
||||
|
||||
// Get request method and path
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$path = isset($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : '/';
|
||||
$path = trim($path, '/');
|
||||
|
||||
// Parse path
|
||||
$pathParts = explode('/', $path);
|
||||
$resource = isset($pathParts[0]) ? $pathParts[0] : '';
|
||||
$id = isset($pathParts[1]) ? $pathParts[1] : null;
|
||||
|
||||
// Health check endpoint
|
||||
if ($path === '' || $path === 'api') {
|
||||
jsonResponse([
|
||||
'message' => 'Epic Travel API is running',
|
||||
'status' => 'healthy'
|
||||
]);
|
||||
}
|
||||
|
||||
// Route to appropriate handler
|
||||
try {
|
||||
switch ($resource) {
|
||||
case 'auth':
|
||||
require __DIR__ . '/api/auth.php';
|
||||
break;
|
||||
|
||||
case 'destinations':
|
||||
require __DIR__ . '/api/destinations.php';
|
||||
break;
|
||||
|
||||
case 'specials':
|
||||
require __DIR__ . '/api/specials.php';
|
||||
break;
|
||||
|
||||
case 'contact':
|
||||
require __DIR__ . '/api/contact.php';
|
||||
break;
|
||||
|
||||
case 'newsletter':
|
||||
require __DIR__ . '/api/newsletter.php';
|
||||
break;
|
||||
|
||||
case 'upload':
|
||||
require __DIR__ . '/api/upload.php';
|
||||
break;
|
||||
|
||||
case 'download':
|
||||
require __DIR__ . '/api/download.php';
|
||||
break;
|
||||
|
||||
case 'testimonials':
|
||||
require __DIR__ . '/api/testimonials.php';
|
||||
break;
|
||||
|
||||
case 'categories':
|
||||
require __DIR__ . '/api/categories.php';
|
||||
break;
|
||||
|
||||
default:
|
||||
jsonResponse(['error' => 'Endpoint not found'], 404);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if (DEBUG_MODE) {
|
||||
jsonResponse(['error' => $e->getMessage()], 500);
|
||||
} else {
|
||||
jsonResponse(['error' => 'Internal server error'], 500);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
// Simple PHP version checker
|
||||
echo "PHP Version: " . phpversion() . "\n";
|
||||
echo "Server Software: " . $_SERVER['SERVER_SOFTWARE'] . "\n";
|
||||
phpinfo();
|
||||
?>
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* Epic Travel - Admin Password Setup
|
||||
* Visit: https://epictravelexpeditions.com/api/setup_password.php
|
||||
* DELETE THIS FILE after use!
|
||||
*/
|
||||
|
||||
$message = '';
|
||||
$success = false;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$password = trim($_POST['password'] ?? '');
|
||||
$confirm = trim($_POST['confirm'] ?? '');
|
||||
|
||||
if (!$email || !$password) {
|
||||
$message = 'Email and password are required.';
|
||||
} elseif ($password !== $confirm) {
|
||||
$message = 'Passwords do not match.';
|
||||
} elseif (strlen($password) < 6) {
|
||||
$message = 'Password must be at least 6 characters.';
|
||||
} else {
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
'mysql:host=localhost;dbname=epic_epic_db;charset=utf8mb4',
|
||||
'root',
|
||||
'b71e5c1a8c7457541b9c1db822de37adfa271926a38b6c20',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
);
|
||||
|
||||
$hash = password_hash($password, PASSWORD_BCRYPT);
|
||||
|
||||
// Check if user exists
|
||||
$check = $pdo->prepare("SELECT COUNT(*) FROM admin_users WHERE email = ?");
|
||||
$check->execute([$email]);
|
||||
|
||||
if ($check->fetchColumn() > 0) {
|
||||
// Update existing
|
||||
$s = $pdo->prepare("UPDATE admin_users SET password_hash = ? WHERE email = ?");
|
||||
$s->execute([$hash, $email]);
|
||||
$message = 'Password updated successfully!';
|
||||
} else {
|
||||
// Create new
|
||||
$s = $pdo->prepare("INSERT INTO admin_users (id, email, password_hash, created_at) VALUES (?, ?, ?, NOW())");
|
||||
$s->execute(['admin-1', $email, $hash]);
|
||||
$message = 'Admin account created successfully!';
|
||||
}
|
||||
|
||||
// Verify
|
||||
if (password_verify($password, $hash)) {
|
||||
$success = true;
|
||||
} else {
|
||||
$message = 'Error: Password verification failed.';
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$message = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin Setup — Epic Travel</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0 }
|
||||
body { background: #0a0f1e; font-family: 'Segoe UI', sans-serif; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px }
|
||||
.box { background: #111827; border: 1px solid rgba(59,130,246,.3); padding: 40px; width: 100%; max-width: 420px }
|
||||
h1 { color: #3b82f6; font-size: 22px; margin-bottom: 6px }
|
||||
.sub { color: #6b7280; font-size: 13px; margin-bottom: 28px }
|
||||
label { display: block; color: #9ca3af; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px }
|
||||
input { width: 100%; background: #1f2937; border: 1px solid rgba(255,255,255,.1); color: #f9fafb; padding: 11px 14px; font-size: 15px; outline: none; margin-bottom: 16px }
|
||||
input:focus { border-color: #3b82f6 }
|
||||
button { width: 100%; padding: 13px; background: #3b82f6; color: #fff; border: none; font-size: 15px; font-weight: 700; cursor: pointer }
|
||||
button:hover { background: #2563eb }
|
||||
.msg { padding: 12px 14px; font-size: 14px; font-weight: 600; margin-bottom: 20px }
|
||||
.msg.error { background: rgba(239,68,68,.1); border: 1px solid rgba(239,68,68,.3); color: #f87171 }
|
||||
.msg.success { background: rgba(34,197,94,.1); border: 1px solid rgba(34,197,94,.3); color: #4ade80 }
|
||||
.warning { background: rgba(245,158,11,.1); border: 1px solid rgba(245,158,11,.3); color: #fbbf24; padding: 12px 14px; font-size: 13px; margin-top: 20px }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<h1>Epic Travel Admin Setup</h1>
|
||||
<div class="sub">Set your admin email and password</div>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="msg <?= $success ? 'success' : 'error' ?>">
|
||||
<?= $success ? '✓ ' : '⚠ ' ?><?= htmlspecialchars($message) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($success): ?>
|
||||
<p style="color:#9ca3af;font-size:14px;margin-bottom:20px">
|
||||
You can now <a href="/admin" style="color:#3b82f6">login to the admin panel</a>.<br><br>
|
||||
<strong style="color:#f87171">⚠ Delete this file immediately!</strong><br>
|
||||
Run in SSH: <code style="background:#1f2937;padding:2px 6px;color:#fbbf24">rm /home/epictravelexpeditions.com/public_html/api/setup_password.php</code>
|
||||
</p>
|
||||
<?php else: ?>
|
||||
<form method="POST">
|
||||
<label>Admin Email</label>
|
||||
<input type="email" name="email" value="admin@epictravelexpeditions.com" required>
|
||||
<label>New Password</label>
|
||||
<input type="password" name="password" placeholder="Enter password" required>
|
||||
<label>Confirm Password</label>
|
||||
<input type="password" name="confirm" placeholder="Confirm password" required>
|
||||
<button type="submit">Set Admin Password</button>
|
||||
</form>
|
||||
<div class="warning">⚠ Delete this file after use. It provides direct DB access.</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user