mirror of
https://github.com/spliit-app/spliit.git
synced 2025-12-06 01:19:29 +01:00
Compare commits
6 Commits
a59352d5da
...
39d55d908a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39d55d908a | ||
|
|
409784672c | ||
|
|
d27cbdba47 | ||
|
|
048ac4da0a | ||
|
|
a86e92e414 | ||
|
|
76c58a7f61 |
45
.github/workflows/cd.yml
vendored
Normal file
45
.github/workflows/cd.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: Create and publish a Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to Github Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Downcase repo
|
||||
run: |
|
||||
echo "REPO=${GITHUB_REPOSITORY@L}" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/${{ env.REPO }}:${{ github.ref_name }}
|
||||
ghcr.io/${{ env.REPO }}:latest
|
||||
provenance: false # Disable provenance to avoid unknown/unknown
|
||||
sbom: false # Disable sbom to avoid unknown/unknown
|
||||
12
README.md
12
README.md
@@ -50,7 +50,7 @@ You can easily add missing translations to the project or even add a new languag
|
||||
Here is the current state of translation:
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/spliit/">
|
||||
<img src="https://hosted.weblate.org/widget/spliit/spliit/horizontal-auto.svg" alt="Translation status" />
|
||||
<img src="https://hosted.weblate.org/widget/spliit/spliit/multi-auto.svg" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
## Run locally
|
||||
@@ -68,6 +68,16 @@ Here is the current state of translation:
|
||||
3. Run `npm run start-container` to start the postgres and the spliit2 containers
|
||||
4. You can access the app by browsing to http://localhost:3000
|
||||
|
||||
You could also pull it from the container registry:
|
||||
```docker pull ghcr.io/spliit-app/spliit:latest```
|
||||
|
||||
## Health check
|
||||
|
||||
The application has a health check endpoint that can be used to check if the application is running and if the database is accessible.
|
||||
|
||||
- `GET /api/health/readiness` or `GET /api/health` - Check if the application is ready to serve requests, including database connectivity.
|
||||
- `GET /api/health/liveness` - Check if the application is running, but not necessarily ready to serve requests.
|
||||
|
||||
## Opt-in features
|
||||
|
||||
### Expense documents
|
||||
|
||||
7
src/app/api/health/liveness/route.ts
Normal file
7
src/app/api/health/liveness/route.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { checkLiveness } from '@/lib/health'
|
||||
|
||||
// Liveness: Is the app itself healthy? (no external dependencies)
|
||||
// If this fails, Kubernetes should restart the pod
|
||||
export async function GET() {
|
||||
return checkLiveness()
|
||||
}
|
||||
7
src/app/api/health/readiness/route.ts
Normal file
7
src/app/api/health/readiness/route.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { checkReadiness } from '@/lib/health'
|
||||
|
||||
// Readiness: Can the app serve requests? (includes all external dependencies)
|
||||
// If this fails, Kubernetes should stop sending traffic but not restart
|
||||
export async function GET() {
|
||||
return checkReadiness()
|
||||
}
|
||||
7
src/app/api/health/route.ts
Normal file
7
src/app/api/health/route.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { checkReadiness } from '@/lib/health'
|
||||
|
||||
// Default health check - same as readiness (includes database check)
|
||||
// This is readiness-focused for monitoring tools like uptime-kuma
|
||||
export async function GET() {
|
||||
return checkReadiness()
|
||||
}
|
||||
96
src/lib/health.ts
Normal file
96
src/lib/health.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export interface HealthCheckStatus {
|
||||
status: 'healthy' | 'unhealthy'
|
||||
services?: {
|
||||
database?: {
|
||||
status: 'healthy' | 'unhealthy'
|
||||
error?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function checkDatabase(): Promise<{
|
||||
status: 'healthy' | 'unhealthy'
|
||||
error?: string
|
||||
}> {
|
||||
try {
|
||||
// Simple query to test database connectivity
|
||||
await prisma.$queryRaw`SELECT 1`
|
||||
return {
|
||||
status: 'healthy',
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'unhealthy',
|
||||
error:
|
||||
error instanceof Error ? error.message : 'Database connection failed',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createHealthResponse(
|
||||
data: HealthCheckStatus,
|
||||
isHealthy: boolean,
|
||||
): Response {
|
||||
return new Response(JSON.stringify(data), {
|
||||
status: isHealthy ? 200 : 503,
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export async function checkReadiness(): Promise<Response> {
|
||||
try {
|
||||
const databaseStatus = await checkDatabase()
|
||||
|
||||
const services: HealthCheckStatus['services'] = {
|
||||
database: databaseStatus,
|
||||
}
|
||||
|
||||
// For readiness: healthy only if all services are healthy
|
||||
const isHealthy = databaseStatus.status === 'healthy'
|
||||
|
||||
const healthStatus: HealthCheckStatus = {
|
||||
status: isHealthy ? 'healthy' : 'unhealthy',
|
||||
services,
|
||||
}
|
||||
|
||||
return createHealthResponse(healthStatus, isHealthy)
|
||||
} catch (error) {
|
||||
const errorStatus: HealthCheckStatus = {
|
||||
status: 'unhealthy',
|
||||
services: {
|
||||
database: {
|
||||
status: 'unhealthy',
|
||||
error:
|
||||
error instanceof Error ? error.message : 'Readiness check failed',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return createHealthResponse(errorStatus, false)
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkLiveness(): Promise<Response> {
|
||||
try {
|
||||
// Liveness: Only check if the app process is alive
|
||||
// No database or external service checks - restarting won't fix those
|
||||
const healthStatus: HealthCheckStatus = {
|
||||
status: 'healthy',
|
||||
// No services reported - we don't check them for liveness
|
||||
}
|
||||
|
||||
return createHealthResponse(healthStatus, true) // Always 200 for liveness
|
||||
} catch (error) {
|
||||
// This should rarely happen, but if it does, the app needs restart
|
||||
const errorStatus: HealthCheckStatus = {
|
||||
status: 'unhealthy',
|
||||
}
|
||||
|
||||
return createHealthResponse(errorStatus, false)
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,8 @@ function getAcceptLanguageLocale(requestHeaders: Headers, locales: Locales) {
|
||||
try {
|
||||
locale = match(languages, locales, defaultLocale)
|
||||
} catch (e) {
|
||||
// invalid language
|
||||
// invalid language - fallback to default
|
||||
locale = defaultLocale
|
||||
}
|
||||
return locale
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user