From ded79a9a9e6cf493be20be94f695192bae395086 Mon Sep 17 00:00:00 2001 From: Antigravity Bot Date: Sun, 30 Aug 2026 08:20:43 +0330 Subject: [PATCH] feat: migrate backend to high-performance PHP 8.5 with SQLite PDO --- api.php | 334 ++++++++++++++++++++++++++++++++++++++++++++++++ db.php | 273 +++++++++++++++++++++++++++++++++++++++ index.php | 3 + router.php | 45 +++++++ urban_issues.db | Bin 32768 -> 32768 bytes 5 files changed, 655 insertions(+) create mode 100644 api.php create mode 100644 db.php create mode 100644 index.php create mode 100644 router.php diff --git a/api.php b/api.php new file mode 100644 index 0000000..bfe6d6d --- /dev/null +++ b/api.php @@ -0,0 +1,334 @@ +query("SELECT COUNT(*) FROM issues")->fetchColumn(); + $pending = (int)$pdo->query("SELECT COUNT(*) FROM issues WHERE status = 'pending'")->fetchColumn(); + $reviewing = (int)$pdo->query("SELECT COUNT(*) FROM issues WHERE status = 'reviewing'")->fetchColumn(); + $inProgress = (int)$pdo->query("SELECT COUNT(*) FROM issues WHERE status = 'in_progress'")->fetchColumn(); + $resolved = (int)$pdo->query("SELECT COUNT(*) FROM issues WHERE status = 'resolved'")->fetchColumn(); + $upvotes = (int)$pdo->query("SELECT COALESCE(SUM(upvotes), 0) FROM issues")->fetchColumn(); + + $catStmt = $pdo->query("SELECT category, COUNT(*) as count FROM issues GROUP BY category ORDER BY count DESC"); + $categories = $catStmt->fetchAll(); + + $distStmt = $pdo->query("SELECT district, COUNT(*) as count FROM issues GROUP BY district ORDER BY district ASC"); + $districts = $distStmt->fetchAll(); + + $prioStmt = $pdo->query("SELECT priority, COUNT(*) as count FROM issues GROUP BY priority"); + $priorities = []; + foreach ($prioStmt->fetchAll() as $row) { + $priorities[$row['priority']] = (int)$row['count']; + } + + $resolutionRate = $total > 0 ? round(($resolved / $total) * 100, 1) : 0; + + jsonResponse([ + 'total' => $total, + 'pending' => $pending, + 'reviewing' => $reviewing, + 'in_progress' => $inProgress, + 'resolved' => $resolved, + 'total_upvotes' => $upvotes, + 'categories' => $categories, + 'districts' => $districts, + 'priorities' => $priorities, + 'resolution_rate' => $resolutionRate + ]); +} + +// 2. GET /api/issues +if ($requestUri === '/api/issues' && $method === 'GET') { + $category = $_GET['category'] ?? null; + $status = $_GET['status'] ?? null; + $district = isset($_GET['district']) ? (int)$_GET['district'] : 0; + $priority = $_GET['priority'] ?? null; + $search = $_GET['search'] ?? null; + $sortBy = $_GET['sort_by'] ?? 'newest'; + + $sql = "SELECT * FROM issues WHERE 1=1"; + $params = []; + + if ($category && $category !== 'all') { + $sql .= " AND category = ?"; + $params[] = $category; + } + + if ($status && $status !== 'all') { + $sql .= " AND status = ?"; + $params[] = $status; + } + + if ($district > 0) { + $sql .= " AND district = ?"; + $params[] = $district; + } + + if ($priority && $priority !== 'all') { + $sql .= " AND priority = ?"; + $params[] = $priority; + } + + if ($search) { + $sql .= " AND (title LIKE ? OR description LIKE ? OR address LIKE ? OR tracking_code LIKE ?)"; + $searchParam = "%{$search}%"; + $params[] = $searchParam; + $params[] = $searchParam; + $params[] = $searchParam; + $params[] = $searchParam; + } + + if ($sortBy === 'upvotes') { + $sql .= " ORDER BY upvotes DESC, id DESC"; + } elseif ($sortBy === 'oldest') { + $sql .= " ORDER BY id ASC"; + } elseif ($sortBy === 'emergency') { + $sql .= " ORDER BY CASE priority WHEN 'emergency' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 ELSE 4 END, id DESC"; + } else { + $sql .= " ORDER BY id DESC"; + } + + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + $rows = $stmt->fetchAll(); + + $commentCountStmt = $pdo->prepare("SELECT COUNT(*) FROM comments WHERE issue_id = ?"); + + $issues = []; + foreach ($rows as $row) { + $commentCountStmt->execute([$row['id']]); + $row['comment_count'] = (int)$commentCountStmt->fetchColumn(); + $issues[] = $row; + } + + jsonResponse($issues); +} + +// 3. POST /api/issues (Create new issue) +if ($requestUri === '/api/issues' && $method === 'POST') { + $data = getJsonInput(); + + if (empty($data['title']) || empty($data['category']) || empty($data['address'])) { + jsonResponse(['error' => 'اطلاعات ضروری تکمیل نشده است.'], 400); + } + + $trackingCode = 'SHR-' . rand(10000, 99999); + $now = date('Y-m-d H:i'); + + $stmt = $pdo->prepare("INSERT INTO issues ( + tracking_code, title, category, description, address, district, priority, status, + lat, lng, image_url, reporter_name, reporter_phone, upvotes, created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?, ?, ?, 1, ?)"); + + $stmt->execute([ + $trackingCode, + $data['title'], + $data['category'], + $data['description'] ?? '', + $data['address'], + (int)($data['district'] ?? 1), + $data['priority'] ?? 'medium', + (float)($data['lat'] ?? 35.7538), + (float)($data['lng'] ?? 51.4172), + $data['image_url'] ?? 'https://images.unsplash.com/photo-1515162816999-a0c47dc192f7?auto=format&fit=crop&w=800&q=80', + $data['reporter_name'] ?? 'شهروند مسئول', + $data['reporter_phone'] ?? '', + $now + ]); + + $issueId = $pdo->lastInsertId(); + + // Timeline initial record + $tlStmt = $pdo->prepare("INSERT INTO timeline (issue_id, status, title, description, created_at) VALUES (?, 'pending', 'ثبت اولیه گزارش در سامانه', 'گزارش شما با موفقیت در سامانه شهرنگار ثبت گردید و در نوبت بررسی کارشناسی قرار گرفت.', ?)"); + $tlStmt->execute([$issueId, $now]); + + $fetchStmt = $pdo->prepare("SELECT * FROM issues WHERE id = ?"); + $fetchStmt->execute([$issueId]); + jsonResponse($fetchStmt->fetch(), 201); +} + +// 4. GET /api/track/{code} +if (preg_match('#^/api/track/([^/]+)$#', $requestUri, $matches) && $method === 'GET') { + $code = strtoupper(trim(urldecode($matches[1]))); + $stmt = $pdo->prepare("SELECT * FROM issues WHERE UPPER(tracking_code) = ?"); + $stmt->execute([$code]); + $issue = $stmt->fetch(); + + if (!$issue) { + jsonResponse(['error' => "هیچ گزارشی با کد رهگیری «{$code}» یافت نشد."], 404); + } + + $tlStmt = $pdo->prepare("SELECT * FROM timeline WHERE issue_id = ? ORDER BY id ASC"); + $tlStmt->execute([$issue['id']]); + $issue['timeline'] = $tlStmt->fetchAll(); + + $cmStmt = $pdo->prepare("SELECT * FROM comments WHERE issue_id = ? ORDER BY id DESC"); + $cmStmt->execute([$issue['id']]); + $issue['comments'] = $cmStmt->fetchAll(); + + jsonResponse($issue); +} + +// 5. GET /api/issues/{id} +if (preg_match('#^/api/issues/(\d+)$#', $requestUri, $matches) && $method === 'GET') { + $issueId = (int)$matches[1]; + $stmt = $pdo->prepare("SELECT * FROM issues WHERE id = ?"); + $stmt->execute([$issueId]); + $issue = $stmt->fetch(); + + if (!$issue) { + jsonResponse(['error' => 'گزارش مورد نظر یافت نشد.'], 404); + } + + $tlStmt = $pdo->prepare("SELECT * FROM timeline WHERE issue_id = ? ORDER BY id ASC"); + $tlStmt->execute([$issueId]); + $issue['timeline'] = $tlStmt->fetchAll(); + + $cmStmt = $pdo->prepare("SELECT * FROM comments WHERE issue_id = ? ORDER BY id DESC"); + $cmStmt->execute([$issueId]); + $issue['comments'] = $cmStmt->fetchAll(); + + jsonResponse($issue); +} + +// 6. POST /api/issues/{id}/upvote +if (preg_match('#^/api/issues/(\d+)/upvote$#', $requestUri, $matches) && $method === 'POST') { + $issueId = (int)$matches[1]; + $stmt = $pdo->prepare("UPDATE issues SET upvotes = upvotes + 1 WHERE id = ?"); + $stmt->execute([$issueId]); + + if ($stmt->rowCount() === 0) { + jsonResponse(['error' => 'گزارش یافت نشد.'], 404); + } + + $fetch = $pdo->prepare("SELECT upvotes FROM issues WHERE id = ?"); + $fetch->execute([$issueId]); + $newUpvotes = (int)$fetch->fetchColumn(); + + jsonResponse(['id' => $issueId, 'upvotes' => $newUpvotes]); +} + +// 7. POST /api/issues/{id}/comment +if (preg_match('#^/api/issues/(\d+)/comment$#', $requestUri, $matches) && $method === 'POST') { + $issueId = (int)$matches[1]; + $data = getJsonInput(); + + if (empty($data['content'])) { + jsonResponse(['error' => 'متن نظر نمی‌تواند خالی باشد.'], 400); + } + + $check = $pdo->prepare("SELECT id FROM issues WHERE id = ?"); + $check->execute([$issueId]); + if (!$check->fetch()) { + jsonResponse(['error' => 'گزارش یافت نشد.'], 404); + } + + $now = date('Y-m-d H:i'); + $author = !empty($data['author_name']) ? trim($data['author_name']) : 'شهروند محترم'; + + $stmt = $pdo->prepare("INSERT INTO comments (issue_id, author_name, content, created_at) VALUES (?, ?, ?, ?)"); + $stmt->execute([$issueId, $author, trim($data['content']), $now]); + $commentId = $pdo->lastInsertId(); + + $cm = $pdo->prepare("SELECT * FROM comments WHERE id = ?"); + $cm->execute([$commentId]); + jsonResponse($cm->fetch(), 201); +} + +// 8. PATCH / POST /api/issues/{id}/status (Admin Status Update) +if (preg_match('#^/api/issues/(\d+)/status$#', $requestUri, $matches) && in_array($method, ['PATCH', 'POST'])) { + $issueId = (int)$matches[1]; + $data = getJsonInput(); + + $check = $pdo->prepare("SELECT * FROM issues WHERE id = ?"); + $check->execute([$issueId]); + $current = $check->fetch(); + if (!$current) { + jsonResponse(['error' => 'گزارش یافت نشد.'], 404); + } + + $newStatus = $data['status'] ?? $current['status']; + $officialResponse = $data['official_response'] ?? $current['official_response']; + $resolvedImageUrl = $data['resolved_image_url'] ?? $current['resolved_image_url']; + + $now = date('Y-m-d H:i'); + $resolvedAt = ($newStatus === 'resolved') ? $now : $current['resolved_at']; + + $upd = $pdo->prepare("UPDATE issues SET status = ?, official_response = ?, resolved_image_url = ?, resolved_at = ? WHERE id = ?"); + $upd->execute([$newStatus, $officialResponse, $resolvedImageUrl, $resolvedAt, $issueId]); + + $statusTitles = [ + 'pending' => 'در صف بررسی مجدد', + 'reviewing' => 'بررسی کارشناسی و ارجاع به معاونت مربوطه', + 'in_progress' => 'اعزام اکیپ اجرایی و آغاز عملیات میدانی', + 'resolved' => 'اتمام عملیات و رفع کامل مسئله', + 'rejected' => 'عدم احراز یا خارج از حیطه اختیارات شهرداری' + ]; + + $tlTitle = !empty($data['timeline_title']) ? $data['timeline_title'] : ($statusTitles[$newStatus] ?? "تغییر وضعیت به {$newStatus}"); + $tlDesc = !empty($data['timeline_desc']) ? $data['timeline_desc'] : ($officialResponse ?: 'وضعیت پرونده توسط مدیریت سامانه به‌روزرسانی شد.'); + + $tlStmt = $pdo->prepare("INSERT INTO timeline (issue_id, status, title, description, created_at) VALUES (?, ?, ?, ?, ?)"); + $tlStmt->execute([$issueId, $newStatus, $tlTitle, $tlDesc, $now]); + + $updated = $pdo->prepare("SELECT * FROM issues WHERE id = ?"); + $updated->execute([$issueId]); + jsonResponse($updated->fetch()); +} + +// 9. POST /api/upload +if ($requestUri === '/api/upload' && $method === 'POST') { + if (!isset($_FILES['file'])) { + jsonResponse(['error' => 'فایلی ارسال نشده است.'], 400); + } + + $uploadDir = __DIR__ . '/static/uploads/'; + if (!is_dir($uploadDir)) { + mkdir($uploadDir, 0777, true); + } + + $ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION); + $filename = uniqid('img_', true) . '.' . $ext; + $target = $uploadDir . $filename; + + if (move_uploaded_file($_FILES['file']['tmp_name'], $target)) { + jsonResponse(['url' => '/static/uploads/' . $filename]); + } else { + jsonResponse(['error' => 'خطا در ذخیره‌سازی فایل.'], 500); + } +} + +// Default 404 for API +jsonResponse(['error' => 'مسیر مورد نظر در API یافت نشد.'], 404); diff --git a/db.php b/db.php new file mode 100644 index 0000000..fcc524b --- /dev/null +++ b/db.php @@ -0,0 +1,273 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + return $pdo; +} + +function initDB() { + $pdo = getDB(); + + // Create Issues Table + $pdo->exec("CREATE TABLE IF NOT EXISTS issues ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + tracking_code TEXT UNIQUE NOT NULL, + title TEXT NOT NULL, + category TEXT NOT NULL, + description TEXT NOT NULL, + address TEXT NOT NULL, + district INTEGER NOT NULL, + priority TEXT NOT NULL, + status TEXT NOT NULL, + lat REAL NOT NULL, + lng REAL NOT NULL, + image_url TEXT, + resolved_image_url TEXT, + reporter_name TEXT, + reporter_phone TEXT, + official_response TEXT, + upvotes INTEGER DEFAULT 0, + created_at TEXT NOT NULL, + resolved_at TEXT + )"); + + // Create Comments Table + $pdo->exec("CREATE TABLE IF NOT EXISTS comments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + issue_id INTEGER NOT NULL, + author_name TEXT NOT NULL, + content TEXT NOT NULL, + created_at TEXT NOT NULL, + FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE + )"); + + // Create Timeline Table + $pdo->exec("CREATE TABLE IF NOT EXISTS timeline ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + issue_id INTEGER NOT NULL, + status TEXT NOT NULL, + title TEXT NOT NULL, + description TEXT, + created_at TEXT NOT NULL, + FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE + )"); + + // Check count and seed if empty + $stmt = $pdo->query("SELECT COUNT(*) FROM issues"); + $count = (int)$stmt->fetchColumn(); + + if ($count === 0) { + seedInitialData($pdo); + } +} + +function seedInitialData(PDO $pdo) { + $now = new DateTime(); + + $sampleIssues = [ + [ + 'tracking_code' => 'SHR-78214', + 'title' => 'فرونشست و چاله عمیق در لاین کندرو', + 'category' => 'آسفالت و معابر', + 'description' => 'وجود چاله عمیق با قطر حدود ۱ متر پس از بارندگی اخیر که باعث خسارت به لاستیک خودروها و ترمزهای ناگهانی خطرناک می‌شود.', + 'address' => 'بزرگراه شهید همت، نرسیده به خروجی گاندی، لاین کندرو', + 'district' => 3, + 'priority' => 'emergency', + 'status' => 'in_progress', + 'lat' => 35.7538, + 'lng' => 51.4172, + 'image_url' => 'https://images.unsplash.com/photo-1515162816999-a0c47dc192f7?auto=format&fit=crop&w=800&q=80', + 'resolved_image_url' => null, + 'reporter_name' => 'علی رضایی', + 'reporter_phone' => '09121112233', + 'official_response' => 'اکیپ آسفالت‌ریزی ناحیه ۲ شهرداری منطقه ۳ اعزام گردیده و عملیات زیرسازی و لکه‌گیری در حال انجام است.', + 'upvotes' => 42, + 'days_ago' => 2, + 'resolved_at' => null, + 'timeline' => [ + ['pending', 'ثبت گزارش توسط شهروند', 'گزارش در سامانه ۱۳۷ با موفقیت ثبت شد.'], + ['reviewing', 'تایید کارشناس و ارجاع به معاونت فنی و عمران', 'موضوع به ناحیه ۲ ارجاع داده شد.'], + ['in_progress', 'اعزام اکیپ عملیاتی لکه‌گیری', 'تیم فنی با تجهیزات در محل مستقر شد.'] + ], + 'comments' => [ + ['سارا محمدی', 'دیروز لاستیک ماشین من هم اینجا آسیب دید، ممنون که پیگیری می‌کنید.'], + ['حسین کاظمی', 'امیدوارم امشب تا قبل از ترافیک صبحگاهی تموم بشه.'] + ] + ], + [ + 'tracking_code' => 'SHR-65109', + 'title' => 'خاموشی کامل پایه چراغ‌های روشنایی بوستان', + 'category' => 'روشنایی و برق', + 'description' => 'کل مسیر پیاده‌روی شرقی بوستان ملت در تاریکی مطلق است که باعث کاهش امنیت خانواده‌ها و ورزشکاران شبانه شده است.', + 'address' => 'خیابان ولیعصر، بوستان ملت، ضلع شرقی جنب دریاچه', + 'district' => 3, + 'priority' => 'high', + 'status' => 'resolved', + 'lat' => 35.7801, + 'lng' => 51.4116, + 'image_url' => 'https://images.unsplash.com/photo-1509114397022-ed747cca3f65?auto=format&fit=crop&w=800&q=80', + 'resolved_image_url' => 'https://images.unsplash.com/photo-1517457373958-b7bdd4587205?auto=format&fit=crop&w=800&q=80', + 'reporter_name' => 'مریم احمدی', + 'reporter_phone' => '09123334455', + 'official_response' => 'کابل‌کشی زیرزمینی و تعویض پروژکتورهای معیوب توسط اداره زیباسازی و تاسیسات منطقه انجام و روشنایی کامل برقرار گردید.', + 'upvotes' => 68, + 'days_ago' => 5, + 'resolved_at' => (clone $now)->modify('-1 day')->format('Y-m-d H:i'), + 'timeline' => [ + ['pending', 'ثبت گزارش توسط شهروند', 'گزارش دریافت شد.'], + ['reviewing', 'بررسی میدانی اداره تاسیسات', 'نقص کابل اصلی تایید شد.'], + ['in_progress', 'تعمیرات و تعویض کابل', 'تیم تاسیسات در حال اجرای خط جدید.'], + ['resolved', 'تکمیل و رفع نقص روشنایی', 'چراغ‌ها روشن و مدار به طور کامل تست شد.'] + ], + 'comments' => [ + ['امیر رستمی', 'خیلی سریع درستش کردن، واقعا تشکر از عوامل شهرداری.'], + ['نگار توکلی', 'دیشب رفتم پارک، همه چراغ‌ها درست شده بود.'] + ] + ], + [ + 'tracking_code' => 'SHR-91203', + 'title' => 'انباشت پسماند و سرریز مخزن زباله شهری', + 'category' => 'نظافت و پسماند', + 'description' => 'مخزن مکانیزه زباله شکسته شده و زباله‌ها در پیاده‌رو پخش شده که باعث بوی نامطبوع و تجمع حشرات شده است.', + 'address' => 'خیابان آزادی، تقاطع خیابان استاد معین، پلاک ۴۲', + 'district' => 9, + 'priority' => 'medium', + 'status' => 'pending', + 'lat' => 35.6997, + 'lng' => 51.3486, + 'image_url' => 'https://images.unsplash.com/photo-1532996122724-e3c354a0b15b?auto=format&fit=crop&w=800&q=80', + 'resolved_image_url' => null, + 'reporter_name' => 'سعید کریمی', + 'reporter_phone' => '09355556677', + 'official_response' => 'گزارش در نوبت بررسی ناظر پسماند منطقه قرار گرفته است.', + 'upvotes' => 19, + 'days_ago' => 1, + 'resolved_at' => null, + 'timeline' => [ + ['pending', 'ثبت گزارش در سامانه', 'منتظر بازرسی و تخصیص سطل جدید مکانیزه.'] + ], + 'comments' => [ + ['مهدی بهرامی', 'لطفا سطل‌های پدال‌دار بگذارید که درش بسته بمونه.'] + ] + ], + [ + 'tracking_code' => 'SHR-44390', + 'title' => 'آسیب دیدگی و شکستن شاخه‌های درخت کهنسال بر اثر طوفان', + 'category' => 'فضای سبز و بوستان‌ها', + 'description' => 'شاخه‌های سنگین درخت چنار شکسته و روی سیم‌های برق و پیاده‌رو معلق مانده است، احتمال سقوط روی عابرین وجود دارد.', + 'address' => 'خیابان شریعتی، بالاتر از پل رومی، نبش کوچه یاس', + 'district' => 1, + 'priority' => 'emergency', + 'status' => 'in_progress', + 'lat' => 35.7985, + 'lng' => 51.4332, + 'image_url' => 'https://images.unsplash.com/photo-1542601906990-b4d3fb778b09?auto=format&fit=crop&w=800&q=80', + 'resolved_image_url' => null, + 'reporter_name' => 'پویا سرمدی', + 'reporter_phone' => '09124445566', + 'official_response' => 'اکیپ سازمان بوستان‌ها و آتش‌نشانی با جرثقیل در حال هرس ایمن و رفع خطر هستند.', + 'upvotes' => 54, + 'days_ago' => 1, + 'resolved_at' => null, + 'timeline' => [ + ['pending', 'ثبت فوری گزارش خطر', 'هشدار سقوط شاخه دریافت شد.'], + ['reviewing', 'هماهنگی با سازمان آتش‌نشانی و فضای سبز', 'اعلام وضعیت اضطراری.'], + ['in_progress', 'عملیات رفع خطر و هرس', 'حصارکشی پیاده‌رو و استقرار بالابر.'] + ], + 'comments' => [ + ['رویا صبوری', 'خدا رو شکر سریع اومدن نوار خطر کشیدن.'] + ] + ], + [ + 'tracking_code' => 'SHR-33219', + 'title' => 'سد معبر طولانی مصالح ساختمانی و تخریب پیاده‌رو', + 'category' => 'ساختمان‌سازی و سد معبر', + 'description' => 'یک پروژه ساختمانی بیش از سه هفته است که تمام پیاده‌رو و بخشی از خیابان را با نخاله و داربست بدون راه عبور ایمن مسدود کرده است.', + 'address' => 'سعادت‌آباد، میدان کاج، خیابان مروارید، پلاک ۱۸', + 'district' => 2, + 'priority' => 'high', + 'status' => 'reviewing', + 'lat' => 35.7794, + 'lng' => 51.3758, + 'image_url' => 'https://images.unsplash.com/photo-1541888946425-d0fbb18086f6?auto=format&fit=crop&w=800&q=80', + 'resolved_image_url' => null, + 'reporter_name' => 'فرشید نادری', + 'reporter_phone' => '09127778899', + 'official_response' => 'اخطاریه ماده ۱۰۰ و رفع سد معبر برای مالک صادر شد. مهلت رفع ۴۸ ساعت می‌باشد.', + 'upvotes' => 37, + 'days_ago' => 3, + 'resolved_at' => null, + 'timeline' => [ + ['pending', 'ثبت گزارش تخلف ساختمانی', 'ثبت شد.'], + ['reviewing', 'بازدید مامور اجرای احکام شهرداری', 'صدور اخطاریه رسمی رفع انسداد معبر.'] + ], + 'comments' => [ + ['کامران شمس', 'مادر من با ویلچر مجبور شد بره توی خیابان که خیلی خطرناک بود!'] + ] + ], + [ + 'tracking_code' => 'SHR-55821', + 'title' => 'خرابی و چشمک‌زن ماندن چراغ راهنمایی تقاطع پرتردد', + 'category' => 'ترافیک و حمل‌ونقل', + 'description' => 'چراغ راهنمایی تقاطع به مدت دو روز چشمک‌زن زرد مانده که باعث گره کور ترافیکی و تصادفات مکرر شده است.', + 'address' => 'میدان فاطمی (جهاد)، تقاطع خیابان جویبار و شهید گمنام', + 'district' => 6, + 'priority' => 'high', + 'status' => 'resolved', + 'lat' => 35.7208, + 'lng' => 51.4082, + 'image_url' => 'https://images.unsplash.com/photo-1517649763962-0c623266ddc0?auto=format&fit=crop&w=800&q=80', + 'resolved_image_url' => 'https://images.unsplash.com/photo-1508873696983-2df5703bc275?auto=format&fit=crop&w=800&q=80', + 'reporter_name' => 'ندا زمانی', + 'reporter_phone' => '09191113355', + 'official_response' => 'برد الکترونیکی کنترلر چراغ توسط شرکت کنترل ترافیک تهران تعویض و زمان‌بندی هوشمند مجدداً فعال گردید.', + 'upvotes' => 85, + 'days_ago' => 4, + 'resolved_at' => (clone $now)->modify('-2 days')->format('Y-m-d H:i'), + 'timeline' => [ + ['pending', 'ثبت گزارش خرابی علائم ترافیکی', 'گزارش دریافت گردید.'], + ['reviewing', 'ارجاع به شرکت کنترل ترافیک', 'تایید خرابی سخت‌افزاری کنترلر.'], + ['in_progress', 'تعویض برد فرمان و سنسورها', 'عملیات فنی در محل انجام شد.'], + ['resolved', 'راه‌اندازی و اتصال به مرکز کنترل', 'چراغ طبق برنامه زمان‌بندی هوشمند شروع به کار کرد.'] + ], + 'comments' => [ + ['سامان یوسفی', 'دست مریزاد، امروز صبح ترافیک خیلی روان‌تر بود.'] + ] + ] + ]; + + $insertIssue = $pdo->prepare("INSERT INTO issues ( + tracking_code, title, category, description, address, district, priority, status, + lat, lng, image_url, resolved_image_url, reporter_name, reporter_phone, + official_response, upvotes, created_at, resolved_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + + $insertTimeline = $pdo->prepare("INSERT INTO timeline (issue_id, status, title, description, created_at) VALUES (?, ?, ?, ?, ?)"); + $insertComment = $pdo->prepare("INSERT INTO comments (issue_id, author_name, content, created_at) VALUES (?, ?, ?, ?)"); + + foreach ($sampleIssues as $item) { + $createdTime = (clone $now)->modify("-{$item['days_ago']} days")->format('Y-m-d H:i'); + $insertIssue->execute([ + $item['tracking_code'], $item['title'], $item['category'], $item['description'], + $item['address'], $item['district'], $item['priority'], $item['status'], + $item['lat'], $item['lng'], $item['image_url'], $item['resolved_image_url'], + $item['reporter_name'], $item['reporter_phone'], $item['official_response'], + $item['upvotes'], $createdTime, $item['resolved_at'] + ]); + $issueId = $pdo->lastInsertId(); + + foreach ($item['timeline'] as $tl) { + $insertTimeline->execute([$issueId, $tl[0], $tl[1], $tl[2], $createdTime]); + } + + foreach ($item['comments'] as $cm) { + $insertComment->execute([$issueId, $cm[0], $cm[1], $createdTime]); + } + } +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..7246437 --- /dev/null +++ b/index.php @@ -0,0 +1,3 @@ + 'text/css', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'png' => 'image/png', + 'jpg' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'gif' => 'image/gif', + 'svg' => 'image/svg+xml', + 'ico' => 'image/x-icon', + 'woff' => 'font/woff', + 'woff2'=> 'font/woff2', + 'ttf' => 'font/ttf', + ]; + + if (isset($mimes[$ext])) { + header('Content-Type: ' . $mimes[$ext]); + } + readfile($filePath); + exit; +} + +// 3. Serve Frontend Application +if (file_exists(__DIR__ . '/static/index.html')) { + header('Content-Type: text/html; charset=utf-8'); + readfile(__DIR__ . '/static/index.html'); + exit; +} + +echo "سامانه مدیریت مسائل شهری شهرنگار فعال است."; diff --git a/urban_issues.db b/urban_issues.db index e9fd38094c156f3332a60e4753a6030b2b1b47ba..ef35ef9f76ec7df159d7c626742330e3c2874fae 100644 GIT binary patch delta 485 zcmZXQO-lk%6ozLM9Zh3Ya#16AL2?t5gI`%KL?UP-^Z{*J*uqE`N&A9E@oV8CnI9lT zG&P|zjS$fLO}&x%WQjJ@5O ztgMUN`AI2XESQ`fYv}&K5`53KIjAtWbuL4 zk;T6T=(LVF!el0fHy!$;7W4^iD&K{r*zjR0M(cPQqZXkRM9dMBrDf1oqf#^}N+~fW z5h=crkc`j6(4xcQ$};YnD{FW?8P`=M!TFisQD$wA^$Q$V-exxE8K=k`?CgsPvjfo3 zo<@eahE`2~J#?XBLbOY!CMM=?z`&)*AWWMC^Sn$j{nsQso5?`Mv6)KvkIbwZvp0L8 fcv_fMs_3P&@U)mF3SbE}baiJI{4Zqf^1}K95jyhd delta 92 zcmV-i0HgnafC7Mk0+1U41d$v=0R*vNq#pak&qCRpDziswl5+O y0S1%VKN|rNv-Lj@5Cahg;gbhYBe8+E2D9N-BN+w(421v;g|iV5dknLMe0u{Gy&Q-D