diff --git a/.gitignore b/.gitignore index e427ff3..f6334fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ node_modules/ */node_modules/ */build/ + +# Composer +vendor/ +composer.lock +composer.phar diff --git a/analyze.php b/analyze.php new file mode 100644 index 0000000..954333a --- /dev/null +++ b/analyze.php @@ -0,0 +1,92 @@ +prepare("SELECT credits FROM users WHERE id = ?"); +$stmt->execute([$_SESSION['user_id']]); +$user = $stmt->fetch(); + +if (!$user || $user['credits'] <= 0) { + // Redirect or show an error if credits are insufficient + $_SESSION['error_message'] = 'You have no credits left. Please purchase more to continue.'; + header('Location: pricing.php'); + exit(); +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_id'])) { + $uploadId = $_POST['upload_id']; + $userId = $_SESSION['user_id']; + + // Re-verify the upload belongs to the user + $stmt = $pdo->prepare("SELECT * FROM uploads WHERE id = ? AND user_id = ?"); + $stmt->execute([$uploadId, $userId]); + $upload = $stmt->fetch(); + + if ($upload) { + // Deduct one credit BEFORE starting the analysis + $pdo->prepare("UPDATE users SET credits = credits - 1 WHERE id = ?")->execute([$userId]); + + // Update status to 'analyzing' + $updateStmt = $pdo->prepare("UPDATE uploads SET status = 'analyzing' WHERE id = ?"); + $updateStmt->execute([$uploadId]); + + // --- Real CV Service Integration --- + $bearerToken = getenv('INTERNAL_CV_BEARER_TOKEN'); + $cvServiceUrl = 'https://internal-model/analyze'; + + $analysisResult = null; + $newStatus = 'failed'; + + if (!$bearerToken) { + $analysisResult = ['error' => 'Internal server configuration error: CV service token not set.']; + } elseif (empty($upload['file_path']) || !file_exists($upload['file_path'])) { + $analysisResult = ['error' => 'File not found for analysis.']; + } else { + // Prepare cURL request + $ch = curl_init(); + $cfile = new CURLFile($upload['file_path'], mime_content_type($upload['file_path']), basename($upload['file_path'])); + + curl_setopt($ch, CURLOPT_URL, $cvServiceUrl); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, ['image' => $cfile]); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ' . $bearerToken, + 'Accept: application/json', + ]); + // IMPORTANT: In a real production environment, you would not disable SSL verification. + // This is included for local/dev environments with self-signed certificates. + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curlError = curl_error($ch); + curl_close($ch); + + if ($curlError) { + $analysisResult = ['error' => 'cURL Error: ' . $curlError]; + } elseif ($httpCode >= 200 && $httpCode < 300) { + $analysisResult = json_decode($response, true); + $newStatus = 'completed'; + } else { + $analysisResult = ['error' => 'CV service returned HTTP ' . $httpCode, 'response' => $response]; + } + } + + // Store the result and update status + $resultStmt = $pdo->prepare("UPDATE uploads SET status = ?, analysis_result = ? WHERE id = ?"); + $resultStmt->execute([$newStatus, json_encode($analysisResult), $uploadId]); + } +} + +header('Location: index.php'); +exit(); +?> \ No newline at end of file diff --git a/assets/css/custom.css b/assets/css/custom.css index f1ce07e..3a27347 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -32,3 +32,108 @@ body { border-radius: 0.5rem; box-shadow: 0 4px 6px rgba(0,0,0,0.1); } + +/* Auth Forms */ +.auth-form { + max-width: 400px; + margin: 5rem auto; + padding: 2rem; + background: #fff; + border-radius: 0.5rem; + box-shadow: 0 4px 6px rgba(0,0,0,0.1); +} + +.auth-form h1 { + text-align: center; + margin-bottom: 1.5rem; +} + +.form-group { + margin-bottom: 1rem; +} + +.form-group label { + display: block; + margin-bottom: 0.5rem; +} + +.form-group input { + width: 100%; + padding: 0.75rem; + border: 1px solid #ced4da; + border-radius: 0.25rem; +} + +.btn { + display: inline-block; + font-weight: 400; + line-height: 1.5; + color: #fff; + text-align: center; + vertical-align: middle; + cursor: pointer; + user-select: none; + background-color: #007bff; + border: 1px solid #007bff; + padding: 0.75rem 1.25rem; + font-size: 1rem; + border-radius: 0.25rem; + transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; + width: 100%; +} + +.btn:hover { + background-color: #0069d9; + border-color: #0062cc; +} + +.alert { + padding: 1rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: 0.25rem; +} + +.alert-danger { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; +} + +.alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb; +} + +.upload-card { + border-radius: 0.5rem; + box-shadow: 0 4px 6px rgba(0,0,0,0.1); + transition: transform 0.2s ease-in-out; +} + +.upload-card:hover { + transform: translateY(-5px); +} + +.upload-card .card-img-top { + aspect-ratio: 16 / 9; + object-fit: cover; +} + +.analysis-result { + background-color: #f8f9fa; + border-left: 3px solid #007BFF; + padding: 0.5rem; + margin-top: 0.5rem; + font-size: 0.875rem; + border-radius: 0.25rem; +} + +.bounding-box { + position: absolute; + border: 2px solid #ff4d4d; + background-color: rgba(255, 77, 77, 0.2); + box-shadow: 0 0 5px rgba(255, 77, 77, 0.5); + pointer-events: none; /* So it doesn't interfere with image interactions */ +} diff --git a/billing.php b/billing.php new file mode 100644 index 0000000..b7455e9 --- /dev/null +++ b/billing.php @@ -0,0 +1,81 @@ +prepare("SELECT credits FROM users WHERE id = ?"); +$stmt->execute([$userId]); +$user = $stmt->fetch(); +$user_credits = $user ? $user['credits'] : 0; + +// Fetch purchase history +$stmt = $pdo->prepare( + "SELECT p.credits_purchased, p.amount_paid, p.created_at, pl.name as plan_name " . + "FROM purchases p " . + "JOIN plans pl ON p.plan_id = pl.id " . + "WHERE p.user_id = ? ORDER BY p.created_at DESC" +); +$stmt->execute([$userId]); +$purchases = $stmt->fetchAll(PDO::FETCH_ASSOC); + +?> + +
+
+

Billing & Credits

+

View your credit balance and purchase history.

+
+ +
+
+
+
+
Your Credits
+

+ Buy More Credits +
+
+
+
+
+
+
Purchase History
+ + + + + + + + + + + + + + + + + + + + +
DatePackageCreditsAmount
+$
+ +

You have not made any purchases yet.

+ +
+
+
+
+
+ + diff --git a/checkout.php b/checkout.php new file mode 100644 index 0000000..10dfaac --- /dev/null +++ b/checkout.php @@ -0,0 +1,40 @@ + ['card'], + 'line_items' => [[ + 'price' => $price_id, + 'quantity' => 1, + ]], + 'mode' => 'subscription', + 'success_url' => 'http://' . $_SERVER['HTTP_HOST'] . '/index.php?payment=success', + 'cancel_url' => 'http://' . $_SERVER['HTTP_HOST'] . '/pricing.php?payment=cancel', + 'customer_email' => $user_email, + 'client_reference_id' => $_SESSION['user_id'] +]); + +header("Location: " . $checkout_session->url); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c46e7df --- /dev/null +++ b/composer.json @@ -0,0 +1,13 @@ +{ + "name": "flatlogic/vda", + "description": "Vehicle Damage Analysis", + "authors": [ + { + "name": "Flatlogic Bot", + "email": "support@flatlogic.com" + } + ], + "require": { + "stripe/stripe-php": "^18.2" + } +} diff --git a/create-portal-session.php b/create-portal-session.php new file mode 100644 index 0000000..f69004d --- /dev/null +++ b/create-portal-session.php @@ -0,0 +1,57 @@ +prepare("SELECT stripe_customer_id FROM subscriptions WHERE user_id = ? ORDER BY created_at DESC LIMIT 1"); + $stmt->execute([$userId]); + $customerId = $stmt->fetchColumn(); +} catch (PDOException $e) { + die('Could not retrieve customer data.'); +} + +if (!$customerId) { + // This can happen if the subscription was created but the webhook failed. + // Or if the user has no subscription. + header('Location: billing.php?error=nocustomer'); + exit; +} + +// The return URL to which the user will be redirected after managing their billing +$returnUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . '/billing.php'; + +try { + // Create a Billing Portal session + $portalSession = \Stripe\BillingPortal\Session::create([ + 'customer' => $customerId, + 'return_url' => $returnUrl, + ]); + + // Redirect to the session URL + header("Location: " . $portalSession->url); + exit(); +} catch (\Stripe\Exception\ApiErrorException $e) { + // Handle Stripe API errors + // You might want to log this error and show a generic message + die('Stripe API error: ' . $e->getMessage()); +} diff --git a/db/config.php b/db/config.php index 75ce244..d51597c 100644 --- a/db/config.php +++ b/db/config.php @@ -15,3 +15,16 @@ function db() { } return $pdo; } + +function hasActiveSubscription($user_id) { + try { + $pdo = db(); + $stmt = $pdo->prepare("SELECT id FROM subscriptions WHERE user_id = ? AND status = 'active' AND current_period_end > NOW()"); + $stmt->execute([$user_id]); + return $stmt->fetch() !== false; + } catch (PDOException $e) { + // Log error if needed + error_log('Subscription check failed: ' . $e->getMessage()); + return false; + } +} diff --git a/db/migrate.php b/db/migrate.php new file mode 100644 index 0000000..86354e9 --- /dev/null +++ b/db/migrate.php @@ -0,0 +1,49 @@ +exec("CREATE TABLE IF NOT EXISTS `migrations` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `migration` VARCHAR(255) NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP );"); + + // 2. Get all executed migrations + $stmt = $pdo->query("SELECT `migration` FROM `migrations`"); + $executed_migrations = $stmt->fetchAll(PDO::FETCH_COLUMN); + + // 3. Find and execute new migrations + $migration_files = glob(__DIR__ . '/migrations/*.sql'); + sort($migration_files); + + $new_migrations_found = false; + foreach ($migration_files as $migration_file) { + $migration_name = basename($migration_file); + + if (!in_array($migration_name, $executed_migrations)) { + $new_migrations_found = true; + $sql = file_get_contents($migration_file); + + try { + $pdo->exec($sql); + + // 4. Log the new migration + $insert_stmt = $pdo->prepare("INSERT INTO `migrations` (`migration`) VALUES (?)"); + $insert_stmt->execute([$migration_name]); + + echo "Executed migration: " . $migration_name . PHP_EOL; + } catch (PDOException $e) { + // If a specific migration fails, output the error and stop. + die("Migration failed on " . $migration_name . ": " . $e->getMessage() . PHP_EOL); + } + } + } + + if (!$new_migrations_found) { + echo "No new migrations to execute." . PHP_EOL; + } else { + echo "All new migrations executed successfully." . PHP_EOL; + } + +} catch (PDOException $e) { + die("Database operation failed: " . $e->getMessage() . PHP_EOL); +} \ No newline at end of file diff --git a/db/migrations/001_create_users_table.sql b/db/migrations/001_create_users_table.sql new file mode 100644 index 0000000..f6028a2 --- /dev/null +++ b/db/migrations/001_create_users_table.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS `users` ( + `id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY, + `email` VARCHAR(255) NOT NULL UNIQUE, + `password` VARCHAR(255) NOT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/db/migrations/002_create_uploads_table.sql b/db/migrations/002_create_uploads_table.sql new file mode 100644 index 0000000..f8ae255 --- /dev/null +++ b/db/migrations/002_create_uploads_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS `uploads` ( + `id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY, + `user_id` INT(11) UNSIGNED NOT NULL, + `file_path` VARCHAR(255) NOT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/db/migrations/003_add_analysis_columns.sql b/db/migrations/003_add_analysis_columns.sql new file mode 100644 index 0000000..af8bc93 --- /dev/null +++ b/db/migrations/003_add_analysis_columns.sql @@ -0,0 +1,3 @@ +ALTER TABLE `uploads` +ADD COLUMN `status` VARCHAR(50) NOT NULL DEFAULT 'pending', +ADD COLUMN `analysis_result` TEXT DEFAULT NULL; diff --git a/db/migrations/004_create_billing_tables.sql b/db/migrations/004_create_billing_tables.sql new file mode 100644 index 0000000..63aba63 --- /dev/null +++ b/db/migrations/004_create_billing_tables.sql @@ -0,0 +1,21 @@ +CREATE TABLE IF NOT EXISTS `plans` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) NOT NULL, + `stripe_price_id` VARCHAR(255) NOT NULL, + `price` DECIMAL(10, 2) NOT NULL, + `features` TEXT, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS `subscriptions` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `user_id` INT(11) UNSIGNED NOT NULL, + `plan_id` INT NOT NULL, + `stripe_subscription_id` VARCHAR(255) NOT NULL, + `status` VARCHAR(50) NOT NULL, + `current_period_end` TIMESTAMP, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`), + FOREIGN KEY (`plan_id`) REFERENCES `plans`(`id`) +); diff --git a/db/migrations/005_add_credits_system.sql b/db/migrations/005_add_credits_system.sql new file mode 100644 index 0000000..d28ceb6 --- /dev/null +++ b/db/migrations/005_add_credits_system.sql @@ -0,0 +1,2 @@ +ALTER TABLE `users` ADD `credits` INT UNSIGNED NOT NULL DEFAULT 0; +ALTER TABLE `plans` ADD `credits_awarded` INT UNSIGNED NOT NULL DEFAULT 0; diff --git a/db/migrations/006_create_purchases_table.sql b/db/migrations/006_create_purchases_table.sql new file mode 100644 index 0000000..a305946 --- /dev/null +++ b/db/migrations/006_create_purchases_table.sql @@ -0,0 +1,15 @@ +-- Drop the now-obsolete subscriptions table +DROP TABLE IF EXISTS `subscriptions`; + +-- Create a table to log credit purchases +CREATE TABLE IF NOT EXISTS `purchases` ( + `id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY, + `user_id` INT(11) UNSIGNED NOT NULL, + `plan_id` INT NOT NULL, + `stripe_charge_id` VARCHAR(255) NOT NULL, + `credits_purchased` INT(11) NOT NULL, + `amount_paid` DECIMAL(10, 2) NOT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE, + FOREIGN KEY (`plan_id`) REFERENCES `plans`(`id`) ON DELETE RESTRICT +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/db/seed_plans.php b/db/seed_plans.php new file mode 100644 index 0000000..e3b9a92 --- /dev/null +++ b/db/seed_plans.php @@ -0,0 +1,31 @@ +query("SELECT COUNT(*) FROM plans"); + if ($stmt->fetchColumn() > 0) { + echo "Plans table is already seeded." . PHP_EOL; + exit; + } + + // Insert the "Starter" credit pack + // IMPORTANT: Replace 'price_12345' with your actual Stripe Price ID for the credit pack + $starterPack = [ + 'name' => 'Starter Pack', + 'stripe_price_id' => 'price_1PeP3QRpH4kRz8A8e25a25fA', // Placeholder - REPLACE THIS + 'price' => 5.00, + 'credits_awarded' => 50, + 'features' => json_encode(['50 analysis credits', 'Standard support']), + ]; + + $sql = "INSERT INTO plans (name, stripe_price_id, price, credits_awarded, features) VALUES (:name, :stripe_price_id, :price, :credits_awarded, :features)"; + $stmt = $pdo->prepare($sql); + $stmt->execute($starterPack); + + echo "Successfully seeded the 'plans' table with the Starter Pack." . PHP_EOL; + +} catch (PDOException $e) { + die("Database error: " . $e->getMessage()); +} diff --git a/index.php b/index.php index 3602744..3c930fe 100644 --- a/index.php +++ b/index.php @@ -1,9 +1,24 @@ prepare("SELECT credits FROM users WHERE id = ?"); +$stmt->execute([$_SESSION['user_id']]); +$user = $stmt->fetch(); +$user_credits = $user ? $user['credits'] : 0; + $upload_dir = 'uploads/'; $uploaded_file_path = null; $error_message = null; -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['vehicleImage'])) { +if ($user_credits > 0 && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['vehicleImage'])) { if ($_FILES['vehicleImage']['error'] === UPLOAD_ERR_OK) { $tmp_name = $_FILES['vehicleImage']['tmp_name']; $name = basename($_FILES['vehicleImage']['name']); @@ -17,6 +32,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['vehicleImage'])) { if (move_uploaded_file($tmp_name, $destination)) { $uploaded_file_path = $destination; + try { + $stmt = db()->prepare("INSERT INTO uploads (user_id, file_path) VALUES (?, ?)"); + $stmt->execute([$_SESSION['user_id'], $uploaded_file_path]); + } catch (PDOException $e) { + $error_message = "Database error: " . $e->getMessage(); + // Optionally, delete the uploaded file if DB insertion fails + unlink($destination); + } } else { $error_message = 'Failed to move uploaded file.'; } @@ -28,81 +51,145 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['vehicleImage'])) { } } ?> - - - - - - Acyfer Vision - - - - - - - - - - - - - -
-
-

Acyfer Vision

-
-
-
-
-
-

Upload Vehicle Photo

-

Get an instant AI-powered damage analysis. Drag and drop an image or click to select a file.

-
-
- -

Drag & drop or click to browse

-

Supports: JPG, PNG, GIF

-
- - -
-
-
- -
- -
- +
+

My Dashboard

+
+ Credits: + Buy More +
+
- -
-

Analysis Result

-
- Uploaded Vehicle Image -
-
Image Received
-

- Status: Pending Analysis -

+ 0): ?> +
+
+
+

Upload Vehicle Photo

+

Get an instant AI-powered damage analysis. Drag and drop an image or click to select a file.

+
+
+ +

Drag & drop or click to browse

+

Supports: JPG, PNG, GIF

+
+ + +
+
+
+ +
+ +
+ + + +
+

Analysis Result

+
+ Uploaded Vehicle Image +
+
Image Received
+

+ Status: Pending Analysis +

+
-
-
- +
+ +
+ +
+

You're Out of Credits!

+

You need to buy more credits to upload and analyze images.

+ Buy Credits +
+ + +
+

My Uploads

+
+ prepare("SELECT * FROM uploads WHERE user_id = ? ORDER BY created_at DESC"); + $stmt->execute([$_SESSION['user_id']]); + $uploads = $stmt->fetchAll(); + + if (count($uploads) > 0): + foreach ($uploads as $upload): + $status = htmlspecialchars($upload['status']); + $status_badge_class = 'bg-secondary'; + if ($status === 'pending') { + $status_badge_class = 'bg-warning text-dark'; + } elseif ($status === 'analyzing') { + $status_badge_class = 'bg-info text-dark'; + } elseif ($status === 'completed') { + $status_badge_class = 'bg-success'; + } elseif ($status === 'failed') { + $status_badge_class = 'bg-danger'; + } + ?> +
+
+ Uploaded Image +
+

+ Uploaded: +

+

+ Status: +

+ + +
+ Analysis Result:
+ Damage Detected:
+ Confidence: %
+
+ +
+ Analysis Failed:
+ +
+ + +
+ 0): ?> + +
+ + +
+ + + + View Report + + + Analyze + +
+
+
+
+ +
+

You haven't uploaded any images yet.

+
+ +
- - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/login.php b/login.php new file mode 100644 index 0000000..936278b --- /dev/null +++ b/login.php @@ -0,0 +1,70 @@ +prepare("SELECT * FROM users WHERE email = ?"); + $stmt->execute([$email]); + $user = $stmt->fetch(); + + if ($user && password_verify($password, $user['password'])) { + $_SESSION['user_id'] = $user['id']; + $_SESSION['user_email'] = $user['email']; + header('Location: index.php'); + exit; + } else { + $errors[] = 'Invalid email or password.'; + } + } catch (PDOException $e) { + $errors[] = 'Database error: ' . $e->getMessage(); + } + } +} +?> + +
+

Login

+ + +
+ +

+ +
+ + +
+
+ + +
+
+ + +
+ +
+

Don't have an account? Register here.

+
+ + diff --git a/logout.php b/logout.php new file mode 100644 index 0000000..95db42c --- /dev/null +++ b/logout.php @@ -0,0 +1,6 @@ + + + + + diff --git a/partials/header.php b/partials/header.php new file mode 100644 index 0000000..05b1a49 --- /dev/null +++ b/partials/header.php @@ -0,0 +1,48 @@ + + + + + + + Vehicle Damage Analysis + + + + + + +
diff --git a/pricing.php b/pricing.php new file mode 100644 index 0000000..3f6b046 --- /dev/null +++ b/pricing.php @@ -0,0 +1,39 @@ +query("SELECT * FROM plans ORDER BY price"); +$plans = $stmt->fetchAll(); + +?> + +
+
+

Purchase Credits

+

One credit buys you one image analysis.

+
+ +
+ +
+
+
+

+
+
+

$

+
    +
  • credits
  • +
  • Never expire
  • +
  • Use them anytime
  • +
+ Buy Now +
+
+
+ +
+
+ + diff --git a/register.php b/register.php new file mode 100644 index 0000000..203e10c --- /dev/null +++ b/register.php @@ -0,0 +1,77 @@ +prepare("SELECT id FROM users WHERE email = ?"); + $stmt->execute([$email]); + if ($stmt->fetch()) { + $errors[] = 'Email already exists.'; + } else { + $hashed_password = password_hash($password, PASSWORD_DEFAULT); + $stmt = $pdo->prepare("INSERT INTO users (email, password) VALUES (?, ?)"); + $stmt->execute([$email, $hashed_password]); + $success = 'Registration successful! You can now log in.'; + } + } catch (PDOException $e) { + $errors[] = 'Database error: ' . $e->getMessage(); + } + } +} +?> + +
+

Register

+ + +
+ +

+ +
+ + + +
+

+
+ +
+
+ + +
+
+ + +
+ +
+

Already have an account? Login here.

+ +
+ + diff --git a/report.php b/report.php new file mode 100644 index 0000000..c400bdf --- /dev/null +++ b/report.php @@ -0,0 +1,94 @@ +prepare("SELECT * FROM uploads WHERE id = ? AND user_id = ? AND status = 'completed'"); +$stmt->execute([$uploadId, $userId]); +$upload = $stmt->fetch(); + +if (!$upload) { + // Redirect if the upload doesn't exist, doesn't belong to the user, or isn't completed + header('Location: index.php'); + exit(); +} + +$analysisResult = json_decode($upload['analysis_result'], true); +$imagePath = htmlspecialchars($upload['file_path']); +list($width, $height) = getimagesize($upload['file_path']); + +include 'partials/header.php'; +?> + +
+
+

Analysis Report

+ Back to Dashboard +
+ +
+
+
+
+

Vehicle Image

+
+ Analyzed Vehicle Image + +
+ +
+
+
+

Report Details

+ +
+ + $value): + if (is_array($value)) continue; // Skip arrays like bounding_box + ?> + + + + + +
+
+
Raw JSON Output
+
+ +

No analysis data available.

+ +
+
+
+
+
+ + diff --git a/uploads/69161db8714608.50876253.jpg b/uploads/69161db8714608.50876253.jpg new file mode 100644 index 0000000..62e138c Binary files /dev/null and b/uploads/69161db8714608.50876253.jpg differ diff --git a/uploads/69161dc883b4f3.88736172.jpg b/uploads/69161dc883b4f3.88736172.jpg new file mode 100644 index 0000000..942e9ab Binary files /dev/null and b/uploads/69161dc883b4f3.88736172.jpg differ diff --git a/webhook.php b/webhook.php new file mode 100644 index 0000000..fc29267 --- /dev/null +++ b/webhook.php @@ -0,0 +1,92 @@ +type) { + case 'checkout.session.completed': + $session = $event->data->object; + handleCheckoutSession($session); + break; + + default: + // Unexpected event type + error_log('Received unknown event type ' . $event->type); +} + +http_response_code(200); + +function handleCheckoutSession($session) { + $userId = $session->client_reference_id; + $stripeChargeId = $session->payment_intent; // Using payment_intent as a proxy for charge ID + + if (!$userId) { + error_log('Webhook Error: No client_reference_id in checkout.session.completed'); + return; + } + + try { + $pdo = db(); + + // Retrieve the line items to find out what was purchased + $line_items = \Stripe\Checkout\Session::allLineItems($session->id, ['limit' => 1]); + $priceId = $line_items->data[0]->price->id; + + // Get plan details from our database + $stmt = $pdo->prepare("SELECT id, credits_awarded, price FROM plans WHERE stripe_price_id = ?"); + $stmt->execute([$priceId]); + $plan = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$plan) { + error_log("Webhook Error: Plan with price ID {$priceId} not found in database."); + return; + } + + $planId = $plan['id']; + $creditsPurchased = $plan['credits_awarded']; + $amountPaid = $plan['price']; + + // Record the purchase + $sql = "INSERT INTO purchases (user_id, plan_id, stripe_charge_id, credits_purchased, amount_paid) VALUES (?, ?, ?, ?, ?)"; + $stmt = $pdo->prepare($sql); + $stmt->execute([$userId, $planId, $stripeChargeId, $creditsPurchased, $amountPaid]); + + // Add credits to the user's account + $sql = "UPDATE users SET credits = credits + ? WHERE id = ?"; + $stmt = $pdo->prepare($sql); + $stmt->execute([$creditsPurchased, $userId]); + + } catch (\Stripe\Exception\ApiErrorException $e) { + error_log("Stripe API Error in webhook: " . $e->getMessage()); + } catch (PDOException $e) { + error_log("Database error in webhook: " . $e->getMessage()); + } +}