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
65 changes: 37 additions & 28 deletions .github/workflows/deploy-demo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,34 +53,43 @@ jobs:

# Since you wanted the database redeployed on every deployment,
# we run a PHP script to clean it, apply schema, and create users.
php -r "
require 'api/db.php';
try {
\$pdo->query('SET FOREIGN_KEY_CHECKS = 0');
\$tables = \$pdo->query('SHOW TABLES')->fetchAll(PDO::FETCH_COLUMN);
foreach (\$tables as \$table) {
\$pdo->query('DROP TABLE IF EXISTS \`' . \$table . '\`');
}

\$sql = file_get_contents('schema.sql');
// Strip statements that might cause permission errors on shared hosting
\$sql = preg_replace('/\bCREATE\s+DATABASE\b[^;]+;/i', '', \$sql);
\$sql = preg_replace('/\bUSE\b\s+[^;]+;/i', '', \$sql);
\$pdo->exec(\$sql);

\$pdo->query('SET FOREIGN_KEY_CHECKS = 1');

\$demoHash = password_hash('demo123', PASSWORD_DEFAULT);

\$stmt = \$pdo->prepare('INSERT INTO users (id, username, email, password_hash, is_admin) VALUES (?, ?, ?, ?, ?)');
\$stmt->execute([bin2hex(random_bytes(18)), 'demo', 'demo@example.com', \$demoHash, 0]);

echo \"Database successfully cleaned, schema imported, and users created!\n\";
} catch (Exception \$e) {
echo \"DB Error: \" . \$e->getMessage() . \"\n\";
exit(1);
}
"
# We do this via HTTP to avoid CLI missing extensions like PDO.
cat << 'EOF' > deploy_reset.php
<?php
require 'api/db.php';
try {
$pdo->query('SET FOREIGN_KEY_CHECKS = 0');
$tables = $pdo->query('SHOW TABLES')->fetchAll(PDO::FETCH_COLUMN);
foreach ($tables as $table) {
$pdo->query('DROP TABLE IF EXISTS `' . $table . '`');
}

$sql = file_get_contents('schema.sql');
// Strip statements that might cause permission errors on shared hosting
$sql = preg_replace('/\bCREATE\s+DATABASE\b[^;]+;/i', '', $sql);
$sql = preg_replace('/\bUSE\b\s+[^;]+;/i', '', $sql);
$pdo->exec($sql);

$pdo->query('SET FOREIGN_KEY_CHECKS = 1');

$demoHash = password_hash('demo123', PASSWORD_DEFAULT);

$stmt = $pdo->prepare('INSERT INTO users (id, username, email, password_hash, is_admin) VALUES (?, ?, ?, ?, ?)');
$stmt->execute([bin2hex(random_bytes(18)), 'demo', 'demo@example.com', $demoHash, 0]);

echo "Database successfully cleaned, schema imported, and users created!\n";
} catch (Exception $e) {
echo "DB Error: " . $e->getMessage() . "\n";
http_response_code(500);
exit(1);
}
EOF

# Execute via web request to ensure correct PHP extensions are loaded
curl -sSf https://nester-demo.philip.ps/backend/deploy_reset.php || { echo "Curl failed"; rm -f deploy_reset.php; exit 1; }

# Clean up
rm -f deploy_reset.php

# Fix permissions for the webserver (assuming the usual www-data or client user)
# chown -R web21:client1 /home/philippsnesterdemo/web/nester-demo/ || true
Expand Down
Binary file added public/logo.af
Binary file not shown.
9 changes: 9 additions & 0 deletions src/contexts/AuthContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ export const AuthProvider = ({ children }) => {
setUser(null);
}, []);

useEffect(() => {
const handleUnauthorized = () => {
logout();
};
window.addEventListener('auth:unauthorized', handleUnauthorized);
return () => window.removeEventListener('auth:unauthorized', handleUnauthorized);
}, [logout]);


// Wrapper around fetch that automatically logs the user out on 401 responses.
// Use this for all authenticated API calls instead of raw fetch().
const fetchWithAuth = useCallback(async (url, options = {}) => {
Expand Down
6 changes: 3 additions & 3 deletions src/screens/UserManagementScreen.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
import { useAuth } from '../contexts/AuthContext';
import { API_BASE_URL } from '../services/nestService';
import { API_BASE_URL, apiFetch } from '../services/nestService';
import './AuthScreens.css';

export default function UserManagementScreen({ onClose }) {
Expand All @@ -14,7 +14,7 @@ export default function UserManagementScreen({ onClose }) {

const fetchUsers = async () => {
try {
const response = await fetch(`${API_BASE_URL}/auth/get_users.php`, {
const response = await apiFetch(`${API_BASE_URL}/auth/get_users.php`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
Expand All @@ -38,7 +38,7 @@ export default function UserManagementScreen({ onClose }) {
setErrorMessage('');

try {
const response = await fetch(`${API_BASE_URL}/auth/create_user.php`, {
const response = await apiFetch(`${API_BASE_URL}/auth/create_user.php`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand Down
10 changes: 5 additions & 5 deletions src/services/authService.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { API_BASE_URL } from './nestService';
import { API_BASE_URL, apiFetch } from './nestService';

// We need to export API_BASE_URL from nestService, so we'll do that shortly as well.

export const login = async (email, password) => {
const response = await fetch(`${API_BASE_URL}/auth/login.php`, {
const response = await apiFetch(`${API_BASE_URL}/auth/login.php`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
Expand All @@ -14,7 +14,7 @@ export const login = async (email, password) => {
};

export const forgotPassword = async (email) => {
const response = await fetch(`${API_BASE_URL}/auth/forgot_password.php`, {
const response = await apiFetch(`${API_BASE_URL}/auth/forgot_password.php`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
Expand All @@ -25,7 +25,7 @@ export const forgotPassword = async (email) => {
};

export const resetPassword = async (token, newPassword) => {
const response = await fetch(`${API_BASE_URL}/auth/reset_password.php`, {
const response = await apiFetch(`${API_BASE_URL}/auth/reset_password.php`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, new_password: newPassword })
Expand All @@ -36,7 +36,7 @@ export const resetPassword = async (token, newPassword) => {
};

export const changePassword = async (token, currentPassword, newPassword) => {
const response = await fetch(`${API_BASE_URL}/auth/change_password.php`, {
const response = await apiFetch(`${API_BASE_URL}/auth/change_password.php`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand Down
38 changes: 29 additions & 9 deletions src/services/nestService.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Dynamically resolve the API base URL from the current server's origin.
// This ensures the app works on any server without build-time configuration.
export const API_BASE_URL = (() => {
if (import.meta.env.VITE_API_BASE_URL) {
return import.meta.env.VITE_API_BASE_URL;
}
const { protocol, host } = window.location;
return `${protocol}//${host}/backend/api`;
})();
Expand All @@ -14,9 +17,26 @@ const getHeaders = (token) => {
return headers;
};

// Wrapper around fetch to handle 401 Unauthorized globally
export const apiFetch = async (url, options = {}) => {
const response = await fetch(url, options);
if (response.status === 401) {
try {
const clone = response.clone();
const data = await clone.json();
if (data.error !== 'Incorrect current password' && data.error !== 'Invalid credentials') {
window.dispatchEvent(new Event('auth:unauthorized'));
}
} catch (e) {
window.dispatchEvent(new Event('auth:unauthorized'));
}
}
return response;
};

export const loadNests = async (token) => {
try {
const response = await fetch(`${API_BASE_URL}/get_nests.php`);
const response = await apiFetch(`${API_BASE_URL}/get_nests.php`);
if (!response.ok) throw new Error('Network response was not ok');
return await response.json();
} catch (e) {
Expand All @@ -35,7 +55,7 @@ export const addNest = async (latlng, name, token) => {
};

try {
const response = await fetch(`${API_BASE_URL}/create_nest.php`, {
const response = await apiFetch(`${API_BASE_URL}/create_nest.php`, {
method: 'POST',
headers: getHeaders(token),
body: JSON.stringify(newNest)
Expand All @@ -59,7 +79,7 @@ export const addLogToNest = async (nestId, actionText, token) => {
};

try {
const response = await fetch(`${API_BASE_URL}/add_log.php`, {
const response = await apiFetch(`${API_BASE_URL}/add_log.php`, {
method: 'POST',
headers: getHeaders(token),
body: JSON.stringify(newLog)
Expand All @@ -74,7 +94,7 @@ export const addLogToNest = async (nestId, actionText, token) => {

export const deleteLogFromNest = async (logId, token) => {
try {
const response = await fetch(`${API_BASE_URL}/delete_log.php`, {
const response = await apiFetch(`${API_BASE_URL}/delete_log.php`, {
method: 'POST',
headers: getHeaders(token),
body: JSON.stringify({ id: logId })
Expand Down Expand Up @@ -103,7 +123,7 @@ export const uploadNestPhoto = async (nestId, photoFile, token) => {
// fetch will automatically set it to multipart/form-data with the correct boundary when body is FormData.

try {
const response = await fetch(`${API_BASE_URL}/upload_nest_photo.php`, {
const response = await apiFetch(`${API_BASE_URL}/upload_nest_photo.php`, {
method: 'POST',
headers: headers,
body: formData
Expand All @@ -118,7 +138,7 @@ export const uploadNestPhoto = async (nestId, photoFile, token) => {

export const deleteNest = async (nestId, token) => {
try {
await fetch(`${API_BASE_URL}/delete_nest.php`, {
await apiFetch(`${API_BASE_URL}/delete_nest.php`, {
method: 'POST',
headers: getHeaders(token),
body: JSON.stringify({ id: nestId })
Expand All @@ -130,7 +150,7 @@ export const deleteNest = async (nestId, token) => {

export const deleteNestPhoto = async (nestId, token) => {
try {
const response = await fetch(`${API_BASE_URL}/delete_nest_photo.php`, {
const response = await apiFetch(`${API_BASE_URL}/delete_nest_photo.php`, {
method: 'POST',
headers: getHeaders(token),
body: JSON.stringify({ id: nestId })
Expand All @@ -145,7 +165,7 @@ export const deleteNestPhoto = async (nestId, token) => {

export const updateNestLocation = async (nestId, latlng, token) => {
try {
await fetch(`${API_BASE_URL}/update_nest.php`, {
await apiFetch(`${API_BASE_URL}/update_nest.php`, {
method: 'POST',
headers: getHeaders(token),
body: JSON.stringify({ id: nestId, lat: latlng.lat, lng: latlng.lng })
Expand All @@ -159,7 +179,7 @@ export const updateNestLocation = async (nestId, latlng, token) => {

export const updateNestName = async (nestId, name, token) => {
try {
const response = await fetch(`${API_BASE_URL}/update_nest_name.php`, {
const response = await apiFetch(`${API_BASE_URL}/update_nest_name.php`, {
method: 'POST',
headers: getHeaders(token),
body: JSON.stringify({ id: nestId, name })
Expand Down
Loading