From c7e40bdd09776474f181115ba0ab6c65d8dccd3c Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 8 Jan 2026 17:14:09 +0000 Subject: [PATCH] 1.1 --- admin/assets/css/style.css | 70 ++++++++ admin/assets/js/main.js | 11 ++ admin/companies.php | 88 ++++++++++ admin/company_delete.php | 23 +++ admin/company_edit.php | 113 ++++++++++++ admin/includes/session.php | 7 + admin/index.php | 100 +++++++++++ admin/logout.php | 3 + admin/project_delete.php | 23 +++ admin/project_edit.php | 171 +++++++++++++++++++ admin/projects.php | 90 ++++++++++ admin/templates/_footer.php | 5 + admin/templates/_header.php | 16 ++ admin/templates/_sidebar.php | 40 +++++ admin/user_delete.php | 19 +++ admin/user_edit.php | 97 +++++++++++ admin/users.php | 64 +++++++ api/auth.php | 61 +++++++ api/companies.php | 140 +++++++++++++++ api/projects.php | 160 +++++++++++++++++ api/users.php | 85 +++++++++ auth.php | 145 ++++++++++++++++ db/migrations/001_initial_schema.sql | 73 ++++++++ db/migrations/002_create_companies_table.sql | 12 ++ db/migrations/003_create_projects_table.sql | 15 ++ login.php | 42 +++++ register.php | 50 ++++++ 27 files changed, 1723 insertions(+) create mode 100644 admin/assets/css/style.css create mode 100644 admin/assets/js/main.js create mode 100644 admin/companies.php create mode 100644 admin/company_delete.php create mode 100644 admin/company_edit.php create mode 100644 admin/includes/session.php create mode 100644 admin/index.php create mode 100644 admin/logout.php create mode 100644 admin/project_delete.php create mode 100644 admin/project_edit.php create mode 100644 admin/projects.php create mode 100644 admin/templates/_footer.php create mode 100644 admin/templates/_header.php create mode 100644 admin/templates/_sidebar.php create mode 100644 admin/user_delete.php create mode 100644 admin/user_edit.php create mode 100644 admin/users.php create mode 100644 api/auth.php create mode 100644 api/companies.php create mode 100644 api/projects.php create mode 100644 api/users.php create mode 100644 auth.php create mode 100644 db/migrations/001_initial_schema.sql create mode 100644 db/migrations/002_create_companies_table.sql create mode 100644 db/migrations/003_create_projects_table.sql create mode 100644 login.php create mode 100644 register.php diff --git a/admin/assets/css/style.css b/admin/assets/css/style.css new file mode 100644 index 0000000..c3fc692 --- /dev/null +++ b/admin/assets/css/style.css @@ -0,0 +1,70 @@ + +:root { + --primary-color: #4F46E5; + --secondary-color: #6B7280; + --bg-light: #F9FAFB; + --surface-color: #FFFFFF; + --border-color: #E5E7EB; +} + +body { + font-family: 'Inter', sans-serif; + background-color: var(--bg-light); +} + +#wrapper { + display: flex; + min-height: 100vh; +} + +#sidebar-wrapper { + min-width: 250px; + max-width: 250px; + background-color: var(--surface-color) !important; + border-right: 1px solid var(--border-color) !important; + transition: margin .25s ease-out; +} + +#wrapper.toggled #sidebar-wrapper { + margin-left: -250px; +} + +#page-content-wrapper { + flex: 1; + min-width: 0; + padding: 1.5rem; +} + +.sidebar-heading { + padding: 1rem 1.25rem; + font-size: 1.2rem; + font-weight: 600; + color: var(--primary-color); +} + +.list-group-item { + border: 0 !important; + color: var(--secondary-color); + font-weight: 500; +} + +.list-group-item.active { + background-color: #EEF2FF !important; + color: var(--primary-color) !important; + border-right: 3px solid var(--primary-color) !important; +} + +.list-group-item-action:hover, .list-group-item-action:focus { + color: var(--primary-color); + background-color: #EEF2FF !important; +} + +.card { + border-radius: 0.75rem; + border: 1px solid var(--border-color); + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.05), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +.card-title { + font-weight: 600; +} diff --git a/admin/assets/js/main.js b/admin/assets/js/main.js new file mode 100644 index 0000000..2d05005 --- /dev/null +++ b/admin/assets/js/main.js @@ -0,0 +1,11 @@ +document.addEventListener("DOMContentLoaded", function() { + const menuToggle = document.getElementById('menu-toggle'); + const wrapper = document.getElementById('wrapper'); + + if (menuToggle) { + menuToggle.addEventListener('click', function (e) { + e.preventDefault(); + wrapper.classList.toggle('toggled'); + }); + } +}); diff --git a/admin/companies.php b/admin/companies.php new file mode 100644 index 0000000..88b3438 --- /dev/null +++ b/admin/companies.php @@ -0,0 +1,88 @@ +prepare("DELETE FROM companies WHERE id = :id"); + $stmt->execute(['id' => $id]); + $_SESSION['success_message'] = 'Company deleted successfully!'; + } catch (PDOException $e) { + $_SESSION['error_message'] = 'Error deleting company: ' . $e->getMessage(); + } + header('Location: companies.php'); + exit(); +} + +// Fetch all companies +$companies = []; +try { + $stmt = $pdo->query("SELECT * FROM companies ORDER BY name"); + $companies = $stmt->fetchAll(); +} catch (PDOException $e) { + $_SESSION['error_message'] = 'Error fetching companies: ' . $e->getMessage(); +} + +$title = 'Companies'; +include __DIR__ . '/../templates/_header.php'; +?> + +

Companies

+ + + + + + + + + +
+ Add New Company +
+ + + + + + + + + + + + + + 0): ?> + + + + + + + + + + + + + + + + +
IDNameEmailPhoneAddressActions
+ Edit + Delete +
No companies found.
+ + diff --git a/admin/company_delete.php b/admin/company_delete.php new file mode 100644 index 0000000..a2cd218 --- /dev/null +++ b/admin/company_delete.php @@ -0,0 +1,23 @@ +prepare("DELETE FROM companies WHERE id = :id"); + $stmt->execute(['id' => $id]); + $_SESSION['success_message'] = 'Company deleted successfully!'; + } catch (PDOException $e) { + $_SESSION['error_message'] = 'Error deleting company: ' . $e->getMessage(); + } +} else { + $_SESSION['error_message'] = 'No company ID provided for deletion.'; +} + +header('Location: companies.php'); +exit(); diff --git a/admin/company_edit.php b/admin/company_edit.php new file mode 100644 index 0000000..30f59f6 --- /dev/null +++ b/admin/company_edit.php @@ -0,0 +1,113 @@ +prepare("SELECT * FROM companies WHERE id = :id"); + $stmt->execute(['id' => $id]); + $company = $stmt->fetch(); + if (!$company) { + $_SESSION['error_message'] = 'Company not found.'; + header('Location: companies.php'); + exit(); + } + } catch (PDOException $e) { + $_SESSION['error_message'] = 'Error fetching company details: ' . $e->getMessage(); + header('Location: companies.php'); + exit(); + } +} + +// Handle form submission +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $name = trim($_POST['name']); + $email = trim($_POST['email']); + $phone = trim($_POST['phone']); + $address = trim($_POST['address']); + + if (empty($name)) { + $errors[] = 'Company Name is required.'; + } + if (!empty($email) && !filter_var($email, FILTER_VALIDATE_EMAIL)) { + $errors[] = 'Invalid Email Address.'; + } + + if (empty($errors)) { + try { + if ($is_edit) { + $stmt = $pdo->prepare("UPDATE companies SET name = :name, email = :email, phone = :phone, address = :address WHERE id = :id"); + $stmt->execute([ + 'name' => $name, + 'email' => $email, + 'phone' => $phone, + 'address' => $address, + 'id' => $id + ]); + $_SESSION['success_message'] = 'Company updated successfully!'; + } else { + $stmt = $pdo->prepare("INSERT INTO companies (name, email, phone, address) VALUES (:name, :email, :phone, :address)"); + $stmt->execute([ + 'name' => $name, + 'email' => $email, + 'phone' => $phone, + 'address' => $address + ]); + $_SESSION['success_message'] = 'Company added successfully!'; + } + header('Location: companies.php'); + exit(); + } catch (PDOException $e) { + $errors[] = 'Database error: ' . $e->getMessage(); + } + } +} + +$title = ($is_edit ? 'Edit Company' : 'Add Company'); +include __DIR__ . '/../templates/_header.php'; +?> + +

+ + + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + Cancel +
+ + diff --git a/admin/includes/session.php b/admin/includes/session.php new file mode 100644 index 0000000..37cf726 --- /dev/null +++ b/admin/includes/session.php @@ -0,0 +1,7 @@ + + + + + +
+ + +
+

Dashboard

+
+ +
+
+
+
+
+
Logged-in Users
+
15
+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
Server Load
+
34%
+
+
+ +
+
+
+
+
+
+ +
+
+ +
+
+
Last 10 Logs
+
+
+

Log data will be displayed here.

+

[Log Entry Placeholder]

+
+
+
+
+ +
+
+
Calendar
+
+
+

A calendar widget will be here.

+
+
+
+
+ +
+
+ + \ No newline at end of file diff --git a/admin/logout.php b/admin/logout.php new file mode 100644 index 0000000..5b0a54b --- /dev/null +++ b/admin/logout.php @@ -0,0 +1,3 @@ +prepare("DELETE FROM projects WHERE id = :id"); + $stmt->execute(['id' => $id]); + $_SESSION['success_message'] = 'Project deleted successfully!'; + } catch (PDOException $e) { + $_SESSION['error_message'] = 'Error deleting project: ' . $e->getMessage(); + } +} else { + $_SESSION['error_message'] = 'No project ID provided for deletion.'; +} + +header('Location: projects.php'); +exit(); diff --git a/admin/project_edit.php b/admin/project_edit.php new file mode 100644 index 0000000..140333c --- /dev/null +++ b/admin/project_edit.php @@ -0,0 +1,171 @@ +query("SELECT id, name FROM companies ORDER BY name"); + $companies = $stmt->fetchAll(); +} catch (PDOException $e) { + $_SESSION['error_message'] = 'Error fetching companies: ' . $e->getMessage(); + header('Location: projects.php'); + exit(); +} + +// Fetch project data if ID is provided (for editing) +if (isset($_GET['id'])) { + $is_edit = true; + $id = $_GET['id']; + try { + $stmt = $pdo->prepare("SELECT * FROM projects WHERE id = :id"); + $stmt->execute(['id' => $id]); + $project = $stmt->fetch(); + if (!$project) { + $_SESSION['error_message'] = 'Project not found.'; + header('Location: projects.php'); + exit(); + } + } catch (PDOException $e) { + $_SESSION['error_message'] = 'Error fetching project details: ' . $e->getMessage(); + header('Location: projects.php'); + exit(); + } +} + +// Handle form submission +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $company_id = $_POST['company_id']; + $name = trim($_POST['name']); + $description = trim($_POST['description']); + $status = $_POST['status']; + $start_date = $_POST['start_date']; + $end_date = $_POST['end_date']; + + if (empty($company_id)) { + $errors[] = 'Company is required.'; + } + if (empty($name)) { + $errors[] = 'Project Name is required.'; + } + if (empty($status)) { + $errors[] = 'Status is required.'; + } + if (!empty($start_date) && !strtotime($start_date)) { + $errors[] = 'Invalid Start Date.'; + } + if (!empty($end_date) && !strtotime($end_date)) { + $errors[] = 'Invalid End Date.'; + } + if (!empty($start_date) && !empty($end_date) && strtotime($start_date) > strtotime($end_date)) { + $errors[] = 'End Date cannot be before Start Date.'; + } + + if (empty($errors)) { + try { + if ($is_edit) { + $stmt = $pdo->prepare("UPDATE projects SET company_id = :company_id, name = :name, description = :description, status = :status, start_date = :start_date, end_date = :end_date WHERE id = :id"); + $stmt->execute([ + 'company_id' => $company_id, + 'name' => $name, + 'description' => $description, + 'status' => $status, + 'start_date' => empty($start_date) ? null : $start_date, + 'end_date' => empty($end_date) ? null : $end_date, + 'id' => $id + ]); + $_SESSION['success_message'] = 'Project updated successfully!'; + } else { + $stmt = $pdo->prepare("INSERT INTO projects (company_id, name, description, status, start_date, end_date) VALUES (:company_id, :name, :description, :status, :start_date, :end_date)"); + $stmt->execute([ + 'company_id' => $company_id, + 'name' => $name, + 'description' => $description, + 'status' => $status, + 'start_date' => empty($start_date) ? null : $start_date, + 'end_date' => empty($end_date) ? null : $end_date + ]); + $_SESSION['success_message'] = 'Project added successfully!'; + } + header('Location: projects.php'); + exit(); + } catch (PDOException $e) { + $errors[] = 'Database error: ' . $e->getMessage(); + } + } +} + +$title = ($is_edit ? 'Edit Project' : 'Add Project'); +include __DIR__ . '/../templates/_header.php'; +?> + +

+ + + + + +
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + Cancel +
+ + diff --git a/admin/projects.php b/admin/projects.php new file mode 100644 index 0000000..ecb586c --- /dev/null +++ b/admin/projects.php @@ -0,0 +1,90 @@ +prepare("DELETE FROM projects WHERE id = :id"); + $stmt->execute(['id' => $id]); + $_SESSION['success_message'] = 'Project deleted successfully!'; + } catch (PDOException $e) { + $_SESSION['error_message'] = 'Error deleting project: ' . $e->getMessage(); + } + header('Location: projects.php'); + exit(); +} + +// Fetch all projects with company name +$projects = []; +try { + $stmt = $pdo->query("SELECT p.*, c.name as company_name FROM projects p JOIN companies c ON p.company_id = c.id ORDER BY p.name"); + $projects = $stmt->fetchAll(); +} catch (PDOException $e) { + $_SESSION['error_message'] = 'Error fetching projects: ' . $e->getMessage(); +} + +$title = 'Projects'; +include __DIR__ . '/../templates/_header.php'; +?> + +

Projects

+ + + + + + + + + +
+ Add New Project +
+ + + + + + + + + + + + + + + 0): ?> + + + + + + + + + + + + + + + + + +
IDNameCompanyStatusStart DateEnd DateActions
+ Edit + Delete +
No projects found.
+ + diff --git a/admin/templates/_footer.php b/admin/templates/_footer.php new file mode 100644 index 0000000..2e102e2 --- /dev/null +++ b/admin/templates/_footer.php @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/admin/templates/_header.php b/admin/templates/_header.php new file mode 100644 index 0000000..221d86d --- /dev/null +++ b/admin/templates/_header.php @@ -0,0 +1,16 @@ + + + + + + + Admin Dashboard + + + + + + + + +
\ No newline at end of file diff --git a/admin/templates/_sidebar.php b/admin/templates/_sidebar.php new file mode 100644 index 0000000..c14ad2e --- /dev/null +++ b/admin/templates/_sidebar.php @@ -0,0 +1,40 @@ + + \ No newline at end of file diff --git a/admin/user_delete.php b/admin/user_delete.php new file mode 100644 index 0000000..50d2510 --- /dev/null +++ b/admin/user_delete.php @@ -0,0 +1,19 @@ +prepare("DELETE FROM users WHERE id = ?"); + $stmt->execute([$id]); + } +} + +header('Location: users.php'); +exit; diff --git a/admin/user_edit.php b/admin/user_edit.php new file mode 100644 index 0000000..1939663 --- /dev/null +++ b/admin/user_edit.php @@ -0,0 +1,97 @@ +prepare("SELECT id, username, email FROM users WHERE id = ?"); + $stmt->execute([$id]); + $user = $stmt->fetch(); + if ($user) { + $is_edit = true; + } +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $username = $_POST['username'] ?? ''; + $email = $_POST['email'] ?? ''; + $password = $_POST['password'] ?? ''; + + // Basic validation + if (empty($username) || empty($email)) { + $error = "Username and email are required."; + } else { + if ($is_edit) { + // Update existing user + if (!empty($password)) { + $hashed_password = password_hash($password, PASSWORD_DEFAULT); + $stmt = db()->prepare("UPDATE users SET username = ?, email = ?, password = ? WHERE id = ?"); + $stmt->execute([$username, $email, $hashed_password, $id]); + } else { + $stmt = db()->prepare("UPDATE users SET username = ?, email = ? WHERE id = ?"); + $stmt->execute([$username, $email, $id]); + } + } else { + // Create new user + if (empty($password)) { + $error = "Password is required for new users."; + } else { + $hashed_password = password_hash($password, PASSWORD_DEFAULT); + $stmt = db()->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)"); + $stmt->execute([$username, $email, $hashed_password]); + } + } + + if (empty($error)) { + header('Location: users.php'); + exit; + } + } +} + +?> + +
+

+ + +
+
+ User Details +
+
+ +
+ +
+
+ + +
+
+ + +
+
+ + > + + Leave blank to keep the current password. + +
+ + Cancel +
+
+
+
+ + diff --git a/admin/users.php b/admin/users.php new file mode 100644 index 0000000..de8f6c4 --- /dev/null +++ b/admin/users.php @@ -0,0 +1,64 @@ +prepare("SELECT id, username, email, created_at FROM users ORDER BY created_at DESC"); +$stmt->execute(); +$users = $stmt->fetchAll(); + +?> + +
+

User Management

+ + + '. + htmlspecialchars($_SESSION['error_message']) . + '
'; + unset($_SESSION['error_message']); + } + ?> + +
+
+ All Users + Add New User +
+
+ + + + + + + + + + + + + + + + + + + + + +
IDUsernameEmailJoinedActions
+ Edit + Delete +
+
+
+
+ + diff --git a/api/auth.php b/api/auth.php new file mode 100644 index 0000000..4503ca8 --- /dev/null +++ b/api/auth.php @@ -0,0 +1,61 @@ + 'Unauthorized', 'message' => 'Invalid API Key.'], 401); + } +} + +checkApiKey(); + +$pdo = db(); +$method = $_SERVER['REQUEST_METHOD']; + +switch ($method) { + case 'POST': + $data = json_decode(file_get_contents('php://input'), true); + if (!$data || empty($data['email']) || empty($data['password'])) { + sendJsonResponse(['error' => 'Bad Request', 'message' => 'Email and password are required.'], 400); + } + + $email = $data['email']; + $password = $data['password']; + + try { + $stmt = $pdo->prepare("SELECT id, name, email, password FROM users WHERE email = :email"); + $stmt->execute(['email' => $email]); + $user = $stmt->fetch(); + + if ($user && password_verify($password, $user['password'])) { + // For a real API, generate and return a token (e.g., JWT) + // For this example, we'll just return a success message and user info (without password) + unset($user['password']); + sendJsonResponse(['message' => 'Login successful.', 'user' => $user]); + } else { + sendJsonResponse(['error' => 'Unauthorized', 'message' => 'Invalid credentials.'], 401); + } + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + break; + + default: + sendJsonResponse(['error' => 'Method Not Allowed', 'message' => '' . $method . ' method is not supported.'], 405); + break; +} diff --git a/api/companies.php b/api/companies.php new file mode 100644 index 0000000..9903c06 --- /dev/null +++ b/api/companies.php @@ -0,0 +1,140 @@ + 'Unauthorized', 'message' => 'Invalid API Key.'], 401); + } +} + +checkApiKey(); + +$pdo = db(); +$method = $_SERVER['REQUEST_METHOD']; + +switch ($method) { + case 'GET': + if (isset($_GET['id'])) { + // Get single company + $id = $_GET['id']; + try { + $stmt = $pdo->prepare("SELECT * FROM companies WHERE id = :id"); + $stmt->execute(['id' => $id]); + $company = $stmt->fetch(); + if ($company) { + sendJsonResponse($company); + } else { + sendJsonResponse(['error' => 'Not Found', 'message' => 'Company not found.'], 404); + } + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + } else { + // Get all companies + try { + $stmt = $pdo->query("SELECT * FROM companies ORDER BY name"); + $companies = $stmt->fetchAll(); + sendJsonResponse($companies); + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + } + break; + + case 'POST': + $data = json_decode(file_get_contents('php://input'), true); + if (!$data || empty($data['name'])) { + sendJsonResponse(['error' => 'Bad Request', 'message' => 'Company name is required.'], 400); + } + + $name = $data['name']; + $email = $data['email'] ?? null; + $phone = $data['phone'] ?? null; + $address = $data['address'] ?? null; + + try { + $stmt = $pdo->prepare("INSERT INTO companies (name, email, phone, address) VALUES (:name, :email, :phone, :address)"); + $stmt->execute([ + 'name' => $name, + 'email' => $email, + 'phone' => $phone, + 'address' => $address + ]); + sendJsonResponse(['message' => 'Company created successfully', 'id' => $pdo->lastInsertId()], 201); + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + break; + + case 'PUT': + $data = json_decode(file_get_contents('php://input'), true); + if (!isset($_GET['id'])) { + sendJsonResponse(['error' => 'Bad Request', 'message' => 'Company ID is required for update.'], 400); + } + if (!$data || empty($data['name'])) { + sendJsonResponse(['error' => 'Bad Request', 'message' => 'Company name is required.'], 400); + } + + $id = $_GET['id']; + $name = $data['name']; + $email = $data['email'] ?? null; + $phone = $data['phone'] ?? null; + $address = $data['address'] ?? null; + + try { + $stmt = $pdo->prepare("UPDATE companies SET name = :name, email = :email, phone = :phone, address = :address WHERE id = :id"); + $stmt->execute([ + 'name' => $name, + 'email' => $email, + 'phone' => $phone, + 'address' => $address, + 'id' => $id + ]); + if ($stmt->rowCount() > 0) { + sendJsonResponse(['message' => 'Company updated successfully.']); + } else { + sendJsonResponse(['error' => 'Not Found', 'message' => 'Company not found or no changes made.'], 404); + } + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + break; + + case 'DELETE': + if (!isset($_GET['id'])) { + sendJsonResponse(['error' => 'Bad Request', 'message' => 'Company ID is required for deletion.'], 400); + } + $id = $_GET['id']; + + try { + $stmt = $pdo->prepare("DELETE FROM companies WHERE id = :id"); + $stmt->execute(['id' => $id]); + if ($stmt->rowCount() > 0) { + sendJsonResponse(['message' => 'Company deleted successfully.']); + } else { + sendJsonResponse(['error' => 'Not Found', 'message' => 'Company not found.'], 404); + } + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + break; + + default: + sendJsonResponse(['error' => 'Method Not Allowed', 'message' => '' . $method . ' method is not supported.'], 405); + break; +} diff --git a/api/projects.php b/api/projects.php new file mode 100644 index 0000000..158dcb8 --- /dev/null +++ b/api/projects.php @@ -0,0 +1,160 @@ + 'Unauthorized', 'message' => 'Invalid API Key.'], 401); + } +} + +checkApiKey(); + +$pdo = db(); +$method = $_SERVER['REQUEST_METHOD']; + +switch ($method) { + case 'GET': + if (isset($_GET['id'])) { + // Get single project + $id = $_GET['id']; + try { + $stmt = $pdo->prepare("SELECT p.*, c.name as company_name FROM projects p JOIN companies c ON p.company_id = c.id WHERE p.id = :id"); + $stmt->execute(['id' => $id]); + $project = $stmt->fetch(); + if ($project) { + sendJsonResponse($project); + } else { + sendJsonResponse(['error' => 'Not Found', 'message' => 'Project not found.'], 404); + } + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + } else if (isset($_GET['company_id'])) { + // Get projects by company + $company_id = $_GET['company_id']; + try { + $stmt = $pdo->prepare("SELECT p.*, c.name as company_name FROM projects p JOIN companies c ON p.company_id = c.id WHERE p.company_id = :company_id ORDER BY p.name"); + $stmt->execute(['company_id' => $company_id]); + $projects = $stmt->fetchAll(); + sendJsonResponse($projects); + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + } + else { + // Get all projects + try { + $stmt = $pdo->query("SELECT p.*, c.name as company_name FROM projects p JOIN companies c ON p.company_id = c.id ORDER BY p.name"); + $projects = $stmt->fetchAll(); + sendJsonResponse($projects); + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + } + break; + + case 'POST': + $data = json_decode(file_get_contents('php://input'), true); + if (!$data || empty($data['company_id']) || empty($data['name'])) { + sendJsonResponse(['error' => 'Bad Request', 'message' => 'Company ID and Project Name are required.'], 400); + } + + $company_id = $data['company_id']; + $name = $data['name']; + $description = $data['description'] ?? null; + $status = $data['status'] ?? 'active'; + $start_date = $data['start_date'] ?? null; + $end_date = $data['end_date'] ?? null; + + try { + $stmt = $pdo->prepare("INSERT INTO projects (company_id, name, description, status, start_date, end_date) VALUES (:company_id, :name, :description, :status, :start_date, :end_date)"); + $stmt->execute([ + 'company_id' => $company_id, + 'name' => $name, + 'description' => $description, + 'status' => $status, + 'start_date' => $start_date, + 'end_date' => $end_date + ]); + sendJsonResponse(['message' => 'Project created successfully', 'id' => $pdo->lastInsertId()], 201); + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + break; + + case 'PUT': + $data = json_decode(file_get_contents('php://input'), true); + if (!isset($_GET['id'])) { + sendJsonResponse(['error' => 'Bad Request', 'message' => 'Project ID is required for update.'], 400); + } + if (!$data || empty($data['company_id']) || empty($data['name'])) { + sendJsonResponse(['error' => 'Bad Request', 'message' => 'Company ID and Project Name are required.'], 400); + } + + $id = $_GET['id']; + $company_id = $data['company_id']; + $name = $data['name']; + $description = $data['description'] ?? null; + $status = $data['status'] ?? 'active'; + $start_date = $data['start_date'] ?? null; + $end_date = $data['end_date'] ?? null; + + try { + $stmt = $pdo->prepare("UPDATE projects SET company_id = :company_id, name = :name, description = :description, status = :status, start_date = :start_date, end_date = :end_date WHERE id = :id"); + $stmt->execute([ + 'company_id' => $company_id, + 'name' => $name, + 'description' => $description, + 'status' => $status, + 'start_date' => $start_date, + 'end_date' => $end_date, + 'id' => $id + ]); + if ($stmt->rowCount() > 0) { + sendJsonResponse(['message' => 'Project updated successfully.']); + } else { + sendJsonResponse(['error' => 'Not Found', 'message' => 'Project not found or no changes made.'], 404); + } + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + break; + + case 'DELETE': + if (!isset($_GET['id'])) { + sendJsonResponse(['error' => 'Bad Request', 'message' => 'Project ID is required for deletion.'], 400); + } + $id = $_GET['id']; + + try { + $stmt = $pdo->prepare("DELETE FROM projects WHERE id = :id"); + $stmt->execute(['id' => $id]); + if ($stmt->rowCount() > 0) { + sendJsonResponse(['message' => 'Project deleted successfully.']); + } else { + sendJsonResponse(['error' => 'Not Found', 'message' => 'Project not found.'], 404); + } + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + break; + + default: + sendJsonResponse(['error' => 'Method Not Allowed', 'message' => '' . $method . ' method is not supported.'], 405); + break; +} diff --git a/api/users.php b/api/users.php new file mode 100644 index 0000000..50669e4 --- /dev/null +++ b/api/users.php @@ -0,0 +1,85 @@ + 'Unauthorized', 'message' => 'Invalid API Key.'], 401); + } +} + +checkApiKey(); + +$pdo = db(); +$method = $_SERVER['REQUEST_METHOD']; + +switch ($method) { + case 'GET': + if (isset($_GET['id'])) { + // Get single user + $id = $_GET['id']; + try { + $stmt = $pdo->prepare("SELECT id, name, email, created_at, updated_at FROM users WHERE id = :id"); + $stmt->execute(['id' => $id]); + $user = $stmt->fetch(); + if ($user) { + sendJsonResponse($user); + } else { + sendJsonResponse(['error' => 'Not Found', 'message' => 'User not found.'], 404); + } + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + } else { + // Get all users + try { + $stmt = $pdo->query("SELECT id, name, email, created_at, updated_at FROM users ORDER BY name"); + $users = $stmt->fetchAll(); + sendJsonResponse($users); + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + } + break; + + case 'POST': + $data = json_decode(file_get_contents('php://input'), true); + if (!$data || empty($data['name']) || empty($data['email']) || empty($data['password'])) { + sendJsonResponse(['error' => 'Bad Request', 'message' => 'Name, email, and password are required.'], 400); + } + + $name = $data['name']; + $email = $data['email']; + $password = password_hash($data['password'], PASSWORD_DEFAULT); + + try { + $stmt = $pdo->prepare("INSERT INTO users (name, email, password) VALUES (:name, :email, :password)"); + $stmt->execute([ + 'name' => $name, + 'email' => $email, + 'password' => $password + ]); + sendJsonResponse(['message' => 'User created successfully', 'id' => $pdo->lastInsertId()], 201); + } catch (PDOException $e) { + sendJsonResponse(['error' => 'Database Error', 'message' => $e->getMessage()], 500); + } + break; + + default: + sendJsonResponse(['error' => 'Method Not Allowed', 'message' => '' . $method . ' method is not supported.'], 405); + break; +} diff --git a/auth.php b/auth.php new file mode 100644 index 0000000..3e58363 --- /dev/null +++ b/auth.php @@ -0,0 +1,145 @@ +prepare('SELECT id FROM users WHERE email = ?'); + $stmt->execute([$email]); + if ($stmt->fetch()) { + header('Location: register.php?error=Email already exists.'); + exit; + } + + $hashed_password = password_hash($password, PASSWORD_DEFAULT); + + $stmt = $pdo->prepare('INSERT INTO users (name, email, password) VALUES (?, ?, ?)'); + if ($stmt->execute([$name, $email, $hashed_password])) { + // Assign default 'User' group + $user_id = $pdo->lastInsertId(); + $stmt = $pdo->prepare('INSERT INTO user_groups (user_id, group_id) VALUES (?, ?)'); + $stmt->execute([$user_id, 3]); + header('Location: login.php'); + exit; + } else { + header('Location: register.php?error=An error occurred.'); + exit; + } +} + +function login() { + if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + header('Location: login.php'); + exit; + } + + $email = $_POST['email'] ?? ''; + $password = $_POST['password'] ?? ''; + + if (empty($email) || empty($password)) { + header('Location: login.php?error=Email and password are required.'); + exit; + } + + $pdo = db(); + $stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?'); + $stmt->execute([$email]); + $user = $stmt->fetch(); + + if ($user && password_verify($password, $user['password'])) { + if ($user['is_suspended']) { + log_login_attempt($user['id'], 'failed'); + header('Location: login.php?error=Your account is suspended.'); + exit; + } + + // Prevent multiple logins from different IPs + $session_id = session_id(); + $ip_address = $_SERVER['REMOTE_ADDR']; + + $stmt = $pdo->prepare('SELECT * FROM active_sessions WHERE user_id = ?'); + $stmt->execute([$user['id']]); + $active_session = $stmt->fetch(); + + if ($active_session && $active_session['ip_address'] !== $ip_address) { + log_login_attempt($user['id'], 'failed'); + header('Location: login.php?error=User is already logged in from another IP address.'); + exit; + } + + // Store session + $stmt = $pdo->prepare('REPLACE INTO active_sessions (session_id, user_id, ip_address) VALUES (?, ?, ?)'); + $stmt->execute([$session_id, $user['id'], $ip_address]); + + $_SESSION['user_id'] = $user['id']; + $_SESSION['user_name'] = $user['name']; + + // Update last login info + $stmt = $pdo->prepare('UPDATE users SET last_login = NOW(), last_login_ip = ? WHERE id = ?'); + $stmt->execute([$ip_address, $user['id']]); + + log_login_attempt($user['id'], 'success'); + + header('Location: admin/index.php'); + exit; + } else { + $user_id = $user ? $user['id'] : null; + log_login_attempt($user_id, 'failed'); + header('Location: login.php?error=Invalid email or password.'); + exit; + } +} + +function logout() { + $pdo = db(); + $stmt = $pdo->prepare('DELETE FROM active_sessions WHERE user_id = ?'); + $stmt->execute([$_SESSION['user_id']]); + + session_unset(); + session_destroy(); + header('Location: login.php'); + exit; +} + +function log_login_attempt($user_id, $status) { + $pdo = db(); + $stmt = $pdo->prepare('INSERT INTO login_logs (user_id, ip_address, status) VALUES (?, ?, ?)'); + $stmt->execute([$user_id, $_SERVER['REMOTE_ADDR'], $status]); +} diff --git a/db/migrations/001_initial_schema.sql b/db/migrations/001_initial_schema.sql new file mode 100644 index 0000000..0b63f8f --- /dev/null +++ b/db/migrations/001_initial_schema.sql @@ -0,0 +1,73 @@ +-- Initial Schema for User Management + +CREATE TABLE IF NOT EXISTS `users` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) NOT NULL, + `email` VARCHAR(255) NOT NULL UNIQUE, + `password` VARCHAR(255) NOT NULL, + `profile_picture` VARCHAR(255) DEFAULT NULL, + `is_suspended` BOOLEAN DEFAULT FALSE, + `api_allowed` BOOLEAN DEFAULT TRUE, + `last_login` DATETIME DEFAULT NULL, + `last_login_ip` VARCHAR(45) DEFAULT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `groups` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) NOT NULL UNIQUE, + `description` TEXT +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `user_groups` ( + `user_id` INT, + `group_id` INT, + PRIMARY KEY (`user_id`, `group_id`), + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE, + FOREIGN KEY (`group_id`) REFERENCES `groups`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `permissions` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) NOT NULL UNIQUE, + `description` TEXT +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `group_permissions` ( + `group_id` INT, + `permission_id` INT, + PRIMARY KEY (`group_id`, `permission_id`), + FOREIGN KEY (`group_id`) REFERENCES `groups`(`id`) ON DELETE CASCADE, + FOREIGN KEY (`permission_id`) REFERENCES `permissions`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `login_logs` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `user_id` INT, + `ip_address` VARCHAR(45), + `login_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `status` ENUM('success', 'failed'), + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS `active_sessions` ( + `session_id` VARCHAR(128) NOT NULL PRIMARY KEY, + `user_id` INT NOT NULL UNIQUE, + `login_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `ip_address` VARCHAR(45), + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB; + +-- Default Data +INSERT INTO `groups` (`id`, `name`, `description`) VALUES +(1, 'SuperAdmin', 'Full access to all system features.'), +(2, 'Admin', 'Administrative access to most features.'), +(3, 'User', 'Standard user access.'); + +-- Default SuperAdmin User (password: password123) +INSERT INTO `users` (`id`, `name`, `email`, `password`) VALUES +(1, 'Super Admin', 'admin@example.com', '$2y$10$fA.o.f8b.5L9zJ/Zc.915eB0GA9gISfCj2L5kI/A5d.4b5I3.f0Fm'); + +INSERT INTO `user_groups` (`user_id`, `group_id`) VALUES (1, 1); + diff --git a/db/migrations/002_create_companies_table.sql b/db/migrations/002_create_companies_table.sql new file mode 100644 index 0000000..b929a6e --- /dev/null +++ b/db/migrations/002_create_companies_table.sql @@ -0,0 +1,12 @@ + +CREATE TABLE IF NOT EXISTS `companies` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `name` VARCHAR(255) NOT NULL, + `address` VARCHAR(255) DEFAULT NULL, + `phone` VARCHAR(20) DEFAULT NULL, + `email` VARCHAR(255) DEFAULT NULL, + `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/db/migrations/003_create_projects_table.sql b/db/migrations/003_create_projects_table.sql new file mode 100644 index 0000000..0de120e --- /dev/null +++ b/db/migrations/003_create_projects_table.sql @@ -0,0 +1,15 @@ + +CREATE TABLE IF NOT EXISTS `projects` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `company_id` INT(11) NOT NULL, + `name` VARCHAR(255) NOT NULL, + `description` TEXT DEFAULT NULL, + `status` VARCHAR(50) DEFAULT 'active', + `start_date` DATE DEFAULT NULL, + `end_date` DATE DEFAULT NULL, + `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `name_company_id` (`name`, `company_id`), + FOREIGN KEY (`company_id`) REFERENCES `companies`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/login.php b/login.php new file mode 100644 index 0000000..380c512 --- /dev/null +++ b/login.php @@ -0,0 +1,42 @@ + + + + + + Login + + + + +
+
+
+
+
+

Login

+ +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+
+
+
+ + diff --git a/register.php b/register.php new file mode 100644 index 0000000..955094a --- /dev/null +++ b/register.php @@ -0,0 +1,50 @@ + + + + + + Register + + + + +
+
+
+
+
+

Register

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