Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Auto-detect text files and normalize line endings to LF on checkin
* text=auto eol=lf

# Explicitly declare text files you want to always be normalized and converted to LF on checkout
*.php text eol=lf
*.js text eol=lf
*.css text eol=lf
*.json text eol=lf
*.md text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.xml text eol=lf
*.sh text eol=lf
*.sql text eol=lf

# Denote all files that are truly binary and should not be modified
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.pdf binary
*.zip binary
*.tar binary
*.gz binary
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ jobs:
run: npm install -g newman

- name: Run Postman tests
run: newman run "./postman/collections/F1 Management API.json" --env-var baseUrl=http://127.0.0.1:8080
run: newman run "./postman/collections/F1 Management API.json" --env-var baseUrl=http://127.0.0.1:8080/api

- name: Display PHP server logs
if: always()
Expand Down
6 changes: 6 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .idea/I425-Team-Project.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 0 additions & 78 deletions .idea/codeStyles/Project.xml

This file was deleted.

5 changes: 0 additions & 5 deletions .idea/codeStyles/codeStyleConfig.xml

This file was deleted.

84 changes: 71 additions & 13 deletions config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

declare(strict_types=1);

use App\Middleware\AuthMiddleware;
use App\Controllers\{AuthController, CarController, DriverController, EventController, TeamController, TrackController};
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\App;
use Slim\Routing\RouteCollectorProxy;

Expand All @@ -22,35 +22,93 @@
return $response;
});

// Team routes
$app->group('/teams', function (RouteCollectorProxy $group) {
// ============================================
// SPA PAGE ROUTES (Serve HTML)
// ============================================
// These routes render PHP pages with embedded JavaScript that call the API endpoints below

$app->get('/', function (Request $request, Response $response) {
ob_start();
require __DIR__ . '/../public/mainPage.php';
$html = ob_get_clean();
$response->getBody()->write($html);
return $response->withHeader('Content-Type', 'text/html');
});

$app->get('/teamsPage', function (Request $request, Response $response) {
ob_start();
require __DIR__ . '/../public/teamsPage.php';
$html = ob_get_clean();
$response->getBody()->write($html);
return $response->withHeader('Content-Type', 'text/html');
});

$app->get('/driversPage', function (Request $request, Response $response) {
ob_start();
require __DIR__ . '/../public/driversPage.php';
$html = ob_get_clean();
$response->getBody()->write($html);
return $response->withHeader('Content-Type', 'text/html');
});

$app->get('/tracksPage', function (Request $request, Response $response) {
ob_start();
require __DIR__ . '/../public/tracksPage.php';
$html = ob_get_clean();
$response->getBody()->write($html);
return $response->withHeader('Content-Type', 'text/html');
});

$app->get('/eventsPage', function (Request $request, Response $response) {
ob_start();
require __DIR__ . '/../public/eventsPage.php';
$html = ob_get_clean();
$response->getBody()->write($html);
return $response->withHeader('Content-Type', 'text/html');
});

$app->get('/carsPage', function (Request $request, Response $response) {
ob_start();
require __DIR__ . '/../public/carsPage.php';
$html = ob_get_clean();
$response->getBody()->write($html);
return $response->withHeader('Content-Type', 'text/html');
});

// ============================================
// API ROUTES (Return JSON)
// ============================================
// All API endpoints are namespaced under /api to prevent collision with SPA page routes

// Team API routes
$app->group('/api/teams', function (RouteCollectorProxy $group) {
$group->get('', TeamController::class . ':getAll');
$group->get('/{id:\d+}', TeamController::class . ':getById');
$group->post('', TeamController::class . ':create');
$group->patch('/{id:\d+}', TeamController::class . ':update');
$group->delete('/{id:\d+}', TeamController::class . ':delete');
});

// Event routes
$app->group('/events', function (RouteCollectorProxy $group) {
// Event API routes
$app->group('/api/events', function (RouteCollectorProxy $group) {
$group->get('', EventController::class . ':getAll');
$group->get('/{id:\d+}', EventController::class . ':getById');
$group->post('', EventController::class . ':create');
$group->patch('/{id:\d+}', EventController::class . ':update');
$group->delete('/{id:\d+}', EventController::class . ':delete');
});

// Track routes
$app->group('/tracks', function (RouteCollectorProxy $group) {
// Track API routes
$app->group('/api/tracks', function (RouteCollectorProxy $group) {
$group->get('', TrackController::class . ':getAllWithParams');
$group->get('/{id:\d+}', TrackController::class . ':getById');
$group->post('', TrackController::class . ':create');
$group->patch('/{id:\d+}', TrackController::class . ':update');
$group->delete('/{id:\d+}', TrackController::class . ':delete');
});

// Driver routes
$app->group('/drivers', function (RouteCollectorProxy $group) {
// Driver API routes
$app->group('/api/drivers', function (RouteCollectorProxy $group) {
$group->get('', DriverController::class . ':getAll');
$group->get('/{id:\d+}', DriverController::class . ':getById');
$group->post('', DriverController::class . ':create');
Expand All @@ -59,17 +117,17 @@
$group->get('/search', DriverController::class . ':search');
});

// Car routes
$app->group('/cars', function (RouteCollectorProxy $group) {
// Car API routes
$app->group('/api/cars', function (RouteCollectorProxy $group) {
$group->get('', CarController::class . ':getAll');
$group->get('/{id:\d+}', CarController::class . ':getById');
$group->post('', CarController::class . ':create');
$group->patch('/{id:\d+}', CarController::class . ':update');
$group->delete('/{id:\d+}', CarController::class . ':delete');
});

// Auth routes TODO need documentation
$app->group('/auth', function (RouteCollectorProxy $group) {
// Auth API routes
$app->group('/api/auth', function (RouteCollectorProxy $group) {
$group->post('/login', AuthController::class . ':login');
$group->post('/register', AuthController::class . ':register');
$group->post('/revoke', AuthController::class . ':revoke');
Expand Down
115 changes: 115 additions & 0 deletions public/carsPage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Formula 1 Cars</title>
<link rel="stylesheet" href="css/global.css">
<script>
let allCars = [];
let currentPage = 1;
const carsPerPage = 5;

document.addEventListener("DOMContentLoaded", function () {
fetchCars();
document.querySelector('#search').addEventListener('input', filterCars);
});

function fetchCars() {
fetch('http://localhost:8080/api/cars')
.then(response => response.json())
.then(cars => {
allCars = cars;
renderCars();
renderPagination();
})
.catch(error => console.error('Error fetching cars:', error));
}

function renderCars() {
const carList = document.querySelector('.car-list');
carList.innerHTML = '';

const filteredCars = getFilteredCars();

const startIndex = (currentPage - 1) * carsPerPage;
const endIndex = startIndex + carsPerPage;
const carsToDisplay = filteredCars.slice(startIndex, endIndex);

carsToDisplay.forEach(car => {
const carItem = document.createElement('div');
carItem.classList.add('car-item');
carItem.innerHTML = `
<h3>Model:</strong> ${car.model}</h3>
<p><strong>Year:</strong> ${car.year}</p>
`;
carList.appendChild(carItem);
});
}

function getFilteredCars() {
const searchQuery = document.querySelector('#search').value.toLowerCase();
return allCars.filter(car => {
return car.model.toLowerCase().includes(searchQuery) ||
car.year.toString().includes(searchQuery);
});
}

function renderPagination() {
const filteredCars = getFilteredCars();
const totalPages = Math.ceil(filteredCars.length / carsPerPage);
const pagination = document.querySelector('.pagination');
pagination.innerHTML = '';

for (let i = 1; i <= totalPages; i++) {
const pageBtn = document.createElement('button');
pageBtn.classList.add('page-btn');
pageBtn.innerText = i;
pageBtn.onclick = () => {
currentPage = i;
renderCars();
};
pagination.appendChild(pageBtn);
}
}

function filterCars() {
currentPage = 1;
renderCars();
renderPagination();
}
</script>
</head>
<body>
<header>
<div class="container">
<h1>Formula 1 Cars</h1>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/tracksPage">Tracks</a></li>
<li><a href="/eventsPage">Events</a></li>
<li><a href="/driversPage">Drivers</a></li>
<li><a href="/teamsPage">Teams</a></li>
<li><a href="/carsPage">Cars</a></li>
<li><a href="/signin" class="signin-btn">Sign In</a></li>
</ul>
</nav>
</div>
</header>
<section id="cars" class="section">
<div class="container">
<h2>Formula 1 Cars</h2>
<p>Explore the cars used by the top teams in Formula 1.</p>
<input type="text" id="search" placeholder="Search by model or year" class="search-box">
<div class="car-list"></div>
<div class="pagination"></div>
</div>
</section>
<footer>
<div class="container">
<p>&copy; 2024 Formula 1 Center. All Rights Reserved.</p>
</div>
</footer>
</body>
</html>
Loading