diff --git a/README.md b/README.md index 05fd3fe..25ec653 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,6 @@ Designed for developers who value both speed and quality, this template provides login-1 register-1 - - ## Quick Start ### 1. Create Your Project @@ -34,13 +32,13 @@ pnpm install | yarn install | npm install 1. Create Environment File ```bash -cp .env.example .env +cp .env.example .env ``` 2. Generate Application Key ```bash -node ace generate:key +node ace generate:key ``` ### 4. Database Setup @@ -48,7 +46,7 @@ node ace generate:key 1. Run Migrations ```bash -ace migration:run +node ace migration:run ``` ### 5. Start Development @@ -56,7 +54,7 @@ ace migration:run Open a terminal and run the following command: ```bash -pn dev +pnpm dev ``` Visit [localhost:3333](http://localhost:3333) and both your frontend and backend will be running together. diff --git a/app/controllers/health_controller.ts b/app/controllers/health_controller.ts new file mode 100644 index 0000000..ae7e155 --- /dev/null +++ b/app/controllers/health_controller.ts @@ -0,0 +1,13 @@ +import { HttpContext } from '@adonisjs/core/http' + +export default class HealthController { + /** + * Get server health status + */ + async index({ response }: HttpContext) { + return response.json({ + status: 'ok', + timestamp: new Date().toISOString(), + }) + } +} diff --git a/resources/js/components/common/health-check.tsx b/resources/js/components/common/health-check.tsx new file mode 100644 index 0000000..d1d2371 --- /dev/null +++ b/resources/js/components/common/health-check.tsx @@ -0,0 +1,102 @@ +import { useEffect, useState } from 'react' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { Skeleton } from '@/components/ui/skeleton' +import axiosInstance from '@/lib/api-client' + +interface HealthStatus { + status: string + timestamp: string +} + +export function HealthCheck() { + const [health, setHealth] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + const fetchHealth = async () => { + try { + const response = await axiosInstance.get('/health') + setHealth(response.data) + setError(null) + } catch (err) { + setError('Failed to fetch health status') + console.error('Health check failed:', err) + } finally { + setLoading(false) + } + } + + fetchHealth() + }, []) + + const getStatusVariant = (status: string) => { + switch (status?.toLowerCase()) { + case 'ok': + return 'default' + case 'error': + return 'destructive' + default: + return 'secondary' + } + } + + const formatTimestamp = (timestamp: string) => { + try { + return new Date(timestamp).toLocaleString() + } catch { + return timestamp + } + } + + if (loading) { + return ( + + + Server Health + Checking server status... + + +
+ + +
+
+
+ ) + } + + return ( + + + Server Health + + {error ? 'Unable to connect to server' : 'Current server status and timestamp'} + + + +
+
+ Status: + {error ? ( + Error + ) : ( + + {health?.status || 'Unknown'} + + )} +
+ {!error && health?.timestamp && ( +
+ Last checked: + + {formatTimestamp(health.timestamp)} + +
+ )} +
+
+
+ ) +} diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx index 82a3b3d..579c8ee 100644 --- a/resources/js/pages/dashboard.tsx +++ b/resources/js/pages/dashboard.tsx @@ -1,4 +1,5 @@ import { PageLayout } from '@/components/layout/page-layout' +import { HealthCheck } from '@/components/common/health-check' import { type BreadcrumbItem } from '@/types' const breadcrumbs: BreadcrumbItem[] = [ @@ -15,7 +16,7 @@ export default function DashboardPage() {
-
+
diff --git a/start/routes.ts b/start/routes.ts index 20078b7..2a7b6b0 100644 --- a/start/routes.ts +++ b/start/routes.ts @@ -8,6 +8,7 @@ */ const SessionController = () => import('#controllers/session_controller') +const HealthController = () => import('#controllers/health_controller') import router from '@adonisjs/core/services/router' import { middleware } from './kernel.js' const RegisteredUsersController = () => import('#controllers/registered_users_controller') @@ -36,6 +37,9 @@ router // API routes // router.any('/api/*', [TrpcController, 'handle']).as('api') + +router.get('/health', [HealthController, 'index']).prefix('api') + router .group(() => { router.post('/login', [SessionController, 'store']).as('login') diff --git a/tests/functional/health.spec.ts b/tests/functional/health.spec.ts new file mode 100644 index 0000000..f9fd4f7 --- /dev/null +++ b/tests/functional/health.spec.ts @@ -0,0 +1,16 @@ +import { test } from '@japa/runner' + +test.group('Health check', () => { + test('should return server health status', async ({ assert }) => { + const response = await fetch('http://localhost:3333/api/health') + const body = (await response.json()) as { status: string; timestamp: string } + + assert.equal(response.status, 200) + assert.equal(body.status, 'ok') + assert.isString(body.timestamp) + + // Verify timestamp is in ISO format + const timestamp = new Date(body.timestamp) + assert.isFalse(Number.isNaN(timestamp.getTime())) + }) +})