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
58 changes: 53 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand All @@ -37,7 +37,10 @@ jobs:
run: composer install --no-interaction --prefer-dist --no-progress

- name: Lint PHP
run: php -l proxy.php
run: |
for file in ./*.php; do
php -l "$file"
done

- name: Validate camera data
run: |
Expand All @@ -47,23 +50,68 @@ jobs:
fwrite(STDERR, "Camera data must be a non-empty array.\n");
exit(1);
}
$ids = [];
foreach ($data as $index => $camera) {
$id = $camera["CamId"] ?? "";
$name = trim((string) ($camera["CamName"] ?? ""));
if (!preg_match("/^[a-f0-9]{24}$/i", (string) $id)) {
fwrite(STDERR, "Invalid CamId at index {$index}: {$id}\n");
exit(1);
}
if (isset($ids[$id])) {
fwrite(STDERR, "Duplicate CamId at index {$index}: {$id}\n");
exit(1);
}
$ids[$id] = true;
if ($name === "") {
fwrite(STDERR, "Missing CamName at index {$index}.\n");
exit(1);
}
}
echo "Validated " . count($data) . " cameras.\n";
echo "Validated " . count($data) . " unique cameras.\n";
'

- name: Smoke-check UI anchors
- name: Validate sitemap
run: |
php sitemap.php > /tmp/camera-sitemap.xml
php -r '
$data = json_decode(file_get_contents("data-camera.json"), true, 512, JSON_THROW_ON_ERROR);
$xml = simplexml_load_file("/tmp/camera-sitemap.xml");
if ($xml === false) {
fwrite(STDERR, "Generated sitemap is invalid XML.\n");
exit(1);
}
$expected = count($data) + 1;
$actual = count($xml->url);
if ($actual !== $expected) {
fwrite(STDERR, "Expected {$expected} sitemap URLs, got {$actual}.\n");
exit(1);
}
echo "Validated {$actual} sitemap URLs.\n";
'
grep -q 'https://camera-hcm.eplus.dev/sitemap.php' sitemap.xml
grep -q 'Sitemap: https://camera-hcm.eplus.dev/sitemap.xml' robots.txt

- name: Smoke-check SEO anchors
run: |
grep -q 'id="cameraGrid"' index.html
grep -q 'id="searchInput"' index.html
grep -q 'proxy.php?id=' index.html
grep -q 'rel="canonical" href="https://camera-hcm.eplus.dev/"' index.html
grep -q 'application/ld+json' index.html
grep -q 'camera.php?id=' index.html
grep -q 'max-image-preview:large' index.html
grep -q 'favicon.svg' index.html
grep -q 'manifest.webmanifest' index.html

- name: HTTP smoke test
run: |
FIRST_ID="$(php -r '$data=json_decode(file_get_contents("data-camera.json"), true, 512, JSON_THROW_ON_ERROR); echo $data[0]["CamId"];')"
php -S 127.0.0.1:8080 > /tmp/camera-php-server.log 2>&1 &
SERVER_PID=$!
trap 'kill "$SERVER_PID"' EXIT
sleep 1

curl -fsS "http://127.0.0.1:8080/" | grep -q 'Camera giao thông TP.HCM'
curl -fsS "http://127.0.0.1:8080/camera.php?id=$FIRST_ID" | grep -q 'https://camera-hcm.eplus.dev/camera.php?id='
curl -fsS "http://127.0.0.1:8080/camera.php?id=$FIRST_ID" | grep -q 'application/ld+json'
curl -fsS "http://127.0.0.1:8080/sitemap.php" | grep -q '<urlset'
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,20 @@ Hình ảnh camera được lấy từ Cổng thông tin giao thông TP.HCM. Tì
---

© ePlus.DEV


## SEO & discovery

Production URL: <https://camera-hcm.eplus.dev/>

The project exposes crawlable resources in addition to the JavaScript camera directory:

- `camera.php?id={CamId}` — server-rendered canonical page for each camera.
- `sitemap.xml` — sitemap index.
- `sitemap.php` — dynamic sitemap containing the homepage and all camera detail pages.
- `robots.txt` — crawler directives and sitemap discovery.
- `llms.txt` — concise AI/LLM discovery file.
- `agents.md` — machine-agent guidance and data semantics.
- `manifest.webmanifest` and `favicon.svg` — app/site identity metadata.

The homepage and detail pages include canonical URLs, social metadata and Schema.org JSON-LD.
31 changes: 31 additions & 0 deletions agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Agent guidance — Camera giao thông TP.HCM

Canonical origin: https://camera-hcm.eplus.dev/

## Purpose

This site helps users find and view public traffic cameras in Ho Chi Minh City by camera name and district/area.

## Preferred discovery flow

1. Use /sitemap.xml or /sitemap.php to discover canonical camera detail pages.
2. Use /data-camera.json for the camera catalog.
3. Use /camera.php?id={CamId} for a human-readable, server-rendered camera page.
4. Treat /proxy.php?id={CamId} as a live image endpoint, not as a stable text document.

## Data semantics

- CamId: stable camera identifier used by this site.
- CamName: human-readable camera/intersection name.
- Disctrict: source field containing district/area information. The misspelling is retained for backward compatibility.
- Snapshot images are time-sensitive and may fail temporarily.

## Crawling

- Respect robots.txt.
- Prefer canonical URLs from sitemap and rel=canonical.
- Avoid generating or indexing arbitrary pagination/search URL combinations when a canonical camera detail page is available.

## Attribution

Site maintained by ePlus.DEV. Camera imagery depends on the Ho Chi Minh City traffic information source.
Loading
Loading