Merge pull request 'Create dashboard layout' (#2) from issue/1 into main

Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
dan
2026-03-22 17:26:20 +00:00
12 changed files with 96 additions and 78 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.DS_Store
database/default-content.sql database/default-content.sql
vendor vendor
node_modules node_modules

View File

@@ -1,15 +0,0 @@
FROM php:8.4-fpm
# Install SQLite extension and Node.js
RUN apt-get update && apt-get install -y \
sqlite3 \
libsqlite3-dev \
curl \
&& docker-php-ext-install pdo_sqlite \
&& curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app
COPY ./src .

37
TODO.md
View File

@@ -1,37 +0,0 @@
# To Do
- [ ] Need to create an agent to send data
- [ ] Need to create api endpoint to handle request
- [ ] Need a new table for accounts
- Users can belong to accounts
## How will authentication work?
JWT or Oauth based tokens
## Lightweight agent
What data will the agent need to send:
1. Gather user data
2. Account id
3. Site id
The agent should be availableas a simple script.
## API Endpoints
```
/api/webhook
```
This will receive a JSON payload from the agent
It will parse the payload to store the data for later display
```
/api/dataset/{account_id}/{site_id}
```
This will provide all metrics to allow displaying
information about the account.:w
```
/api/site-list/{account_id}
```
This will provide a list of sites for a given account.
Each account can have unlimited sites. Each site has an id.

View File

@@ -16,8 +16,6 @@
"guzzlehttp/guzzle": "^7.10" "guzzlehttp/guzzle": "^7.10"
}, },
"scripts": { "scripts": {
"start": "sh scripts/start.sh",
"rebuild": "sh scripts/rebuild.sh",
"theme": "sh scripts/theme.sh", "theme": "sh scripts/theme.sh",
"db-reset": "sh scripts/db-setup.sh" "db-reset": "sh scripts/db-setup.sh"
} }

View File

@@ -55,9 +55,13 @@ CREATE TABLE IF NOT EXISTS user_accounts (
CREATE TABLE IF NOT EXISTS pageviews ( CREATE TABLE IF NOT EXISTS pageviews (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL, account_id TEXT NOT NULL,
page TEXT NOT NULL, page TEXT NOT NULL,
title TEXT,
user_agent TEXT, user_agent TEXT,
screen_res TEXT,
language TEXT,
timestamp TEXT,
referrer TEXT, referrer TEXT,
ip TEXT, ip TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

View File

@@ -1,11 +1,23 @@
services: services:
app: php:
build: . image: dchadwick/planet-express:1.0.1
ports: working_dir: /app
- "127.0.0.1:9000:9000" command: php-fpm -F
volumes: volumes:
- .:/app - .:/app
- ./database:/app/database # Persists your SQLite file - ./database:/app/database
networks:
- app-network
web:
image: nginx:alpine
ports:
- "8000:80"
volumes:
- .:/app
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- php
networks: networks:
- app-network - app-network

View File

@@ -25,7 +25,12 @@ if (!$router->routeExists($path)) {
// now we dont have to do this in every controller. // now we dont have to do this in every controller.
// Still allow login on homepage. // Still allow login on homepage.
$request = new Request(); $request = new Request();
$allowed_paths = ['/', '/user-login']; $allowed_paths = [
'/',
'/user-login',
'/api/v1/webhook'
];
if ( if (
!$request->getCookie('ORIGOSESS') && !$request->getCookie('ORIGOSESS') &&
!in_array($path, $allowed_paths) !in_array($path, $allowed_paths)

32
nginx.conf Normal file
View File

@@ -0,0 +1,32 @@
server {
listen 80;
server_name localhost;
# This MUST match the working_dir and volume path from your docker-compose.yml
root /app;
index index.php index.html;
# Using underscores for log files
access_log /var/log/nginx/access_log.log;
error_log /var/log/nginx/error_log.log;
# Route everything to index.php if the file doesn't explicitly exist
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Pass all .php files to the PHP-FPM container
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# 'php' is the name of the service in your docker-compose.yml
# 9000 is the default port PHP-FPM listens on
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}

View File

@@ -1,2 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
PASSWORD=$1
echo -n $1 | shasum -a 256
openssl rand -hex 16 openssl rand -hex 16

View File

@@ -1,2 +0,0 @@
#!/bin/bash
docker build -t origo .

View File

@@ -1,2 +0,0 @@
#!/bin/bash
docker run --rm -p 8000:8000 -v $(pwd):/app origo php -S 0.0.0.0:8000

View File

@@ -30,30 +30,49 @@ class ApiWebhookController extends ControllerBase implements ControllerInterface
* The response. * The response.
*/ */
public function getResponse(): string { public function getResponse(): string {
header('Access-Control-Allow-Origin: *'); // Allow all origins. // @todo: Create Enum.
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); $allowed_origins = [
header('Access-Control-Allow-Headers: Content-Type, Authorization'); 'http://127.0.0.1:8088',
'http://localhost:8088',
'https://yourproduction-site.com',
'https://another-client-site.io'
];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
// Check if the requester is in our whitelist
if (in_array($origin, $allowed_origins)) {
header("Access-Control-Allow-Origin: $origin");
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
}
// Handle preflight OPTIONS request // Handle preflight OPTIONS request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200); http_response_code(200);
exit(); // Stop here - don't process the request exit(); // Stop here - don't process the request
} }
// Make sure we have a path and account before proceeding.
$path = $this->request->post('path'); $raw_input = file_get_contents('php://input');
$account_id = $this->request->post('account'); $json_data = json_decode($raw_input, true) ?? [];
if ( $path = $json_data['path'] ?? $this->request->post('path');
!$path || $account_id = $json_data['account'] ?? $this->request->post('account');
!$account_id
) { if (!$path || !$account_id) {
// Log what actually arrived to help debugging
error_log("Missing Data - Path: $path, Account: $account_id. Raw: $raw_input");
header("HTTP/1.1 422 Unprocessable Entity"); header("HTTP/1.1 422 Unprocessable Entity");
die(); die("Missing required fields");
} }
// @todo: validate the account id. // @todo: validate the account id.
$entry = [ $entry = [
'page' => $path, 'page' => $path,
'account_id' => $account_id, 'account_id' => $account_id,
'user_agent' => $this->request->post('user_agent'), 'title' => $json_data['title'] ?? null,
'referrer' => $this->request->post('referrer'), 'user_agent' => $json_data['user_agent'] ?? null,
'screen_res' => $json_data['screen_res'] ?? null,
'language' => $json_data['lang'] ?? null,
'timestamp' => $json_data['ts'] ?? null,
'referrer' => $json_data['referrer'] ?? null,
'ip' => $this->request->ip(), 'ip' => $this->request->ip(),
]; ];
$clean_entry = array_filter($entry); $clean_entry = array_filter($entry);