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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
EXPO_PUBLIC_USE_LOCAL_API=1
EXPO_PUBLIC_LOCAL_API_URL=http://192.168.76.129:8080
EXPO_PUBLIC_API_URL=https://devbits.ddns.net
EXPO_PUBLIC_LOCAL_API_PORT=8080
POSTGRES_DB=devbits_dev
POSTGRES_USER=devbits_dev
POSTGRES_PASSWORD=devbits_dev_password
54 changes: 54 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Test

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

jobs:
test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:15
env:
POSTGRES_DB: devbits_test
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass123
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

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

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'

- name: Wait for PostgreSQL
run: |
for i in {1..30}; do
if pg_isready -h localhost -p 5432 -U testuser -d devbits_test; then
exit 0
fi
sleep 2
done
exit 1

- name: Run tests
working-directory: backend
env:
USE_TEST_DB: true
POSTGRES_TEST_DB: devbits_test
POSTGRES_TEST_USER: testuser
POSTGRES_TEST_PASSWORD: testpass123
run: go test -v ./api/internal/tests/...
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dist/
# Local env files
.env
.env.*
!.env.example

# Expo
.expo/
Expand All @@ -38,6 +39,7 @@ dist/
.env
.env.*
*.env
!.env.example

# Secrets folder (recommended place for service keys)
secrets/
Expand Down
95 changes: 95 additions & 0 deletions Bash-Scripts/run-db-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#!/usr/bin/env bash

# Script: run-db-tests.sh
# Does: Recreates isolated local dev DB/backend containers, waits for DB, then runs Go tests in a temporary golang container.
# Use: ./run-db-tests.sh
# DB: devbits_dev via host.docker.internal in compose project devbits-dev-local.
# Ports: backend default :8080, DB default :5433 (DEVBITS_BACKEND_PORT / DEVBITS_DB_PORT override).
# Modes: Frontend=OFF | Backend=only for local test infra | Live stack untouched | Dev/Test data isolated.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
BACKEND_DIR="$ROOT/backend"
COMPOSE_PROJECT="devbits-dev-local"

if ! command -v docker >/dev/null 2>&1; then
echo "Error: Docker is required."
exit 1
fi

if ! docker compose version >/dev/null 2>&1; then
echo "Error: Docker Compose v2 is required."
exit 1
fi

is_port_in_use() {
local port="$1"
if command -v ss >/dev/null 2>&1; then
ss -ltn "sport = :${port}" | grep -q LISTEN
return $?
fi
if command -v lsof >/dev/null 2>&1; then
lsof -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
return $?
fi
netstat -an 2>/dev/null | grep -E "[:.]${port}[[:space:]]" | grep -qi LISTEN
}

resolve_port() {
local label="$1"
local default_port="$2"
local chosen="$default_port"

while is_port_in_use "$chosen"; do
echo "Port ${chosen} is already in use for ${label}."
read -r -p "Enter alternate port for ${label} (blank to exit): " chosen
if [[ -z "$chosen" ]]; then
echo "Exiting. Free port ${default_port} or choose an alternate port next run."
exit 1
fi
if ! [[ "$chosen" =~ ^[0-9]+$ ]] || (( chosen < 1 || chosen > 65535 )); then
echo "Invalid port: ${chosen}"
chosen="$default_port"
fi
done

echo "$chosen"
}

DEVBITS_BACKEND_PORT="$(resolve_port "backend" 8080)"
DEVBITS_DB_PORT="$(resolve_port "postgres" 5433)"
export DEVBITS_BACKEND_PORT
export DEVBITS_DB_PORT

cd "$ROOT"
docker compose -p "$COMPOSE_PROJECT" -f backend/docker-compose.dev.yml down --volumes --remove-orphans
docker compose -p "$COMPOSE_PROJECT" -f backend/docker-compose.dev.yml up -d --build

echo "Waiting for database readiness..."
for i in $(seq 1 60); do
if docker compose -p "$COMPOSE_PROJECT" -f backend/docker-compose.dev.yml exec -T db pg_isready -U devbits_dev -d devbits_dev >/dev/null 2>&1; then
echo "Database is ready."
break
fi
if [[ "$i" -eq 60 ]]; then
echo "Error: Database did not become ready within 60 seconds."
exit 1
fi
sleep 1
done

set +e
docker run --rm --add-host=host.docker.internal:host-gateway \
-e USE_TEST_DB=true \
-e POSTGRES_TEST_DB=devbits_dev \
-e POSTGRES_TEST_USER=devbits_dev \
-e POSTGRES_TEST_PASSWORD=devbits_dev_password \
-e POSTGRES_TEST_HOST=host.docker.internal \
-e POSTGRES_TEST_PORT="$DEVBITS_DB_PORT" \
-v "$ROOT/backend:/app" -w /app/api golang:1.24 bash -c "go test ./..."
TEST_EXIT=$?
set -e

exit $TEST_EXIT
114 changes: 114 additions & 0 deletions Bash-Scripts/run-dev.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env bash

# Script: run-dev.sh
# Does: Boots local dev DB + local backend (isolated compose project), then launches frontend in local mode.
# Use: ./run-dev.sh [--clear]
# DB: devbits_dev (user/pass: devbits_dev/devbits_dev_password) in compose project devbits-dev-local.
# Ports: backend default :8080, DB default :5433 (DEVBITS_BACKEND_PORT / DEVBITS_DB_PORT override).
# Modes: Frontend=ON(local API) | Backend=ON(local Docker) | Live stack untouched | Test DB untouched.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
BACKEND_DIR="$ROOT/backend"
COMPOSE_PROJECT="devbits-dev-local"

CLEAR_FRONTEND=""
if [[ "${1:-}" == "--clear" ]]; then
CLEAR_FRONTEND="--clear"
fi

if ! command -v docker >/dev/null 2>&1; then
echo "Error: Docker is required."
exit 1
fi

if ! docker compose version >/dev/null 2>&1; then
echo "Error: Docker Compose v2 is required."
exit 1
fi

is_port_in_use() {
local port="$1"
if command -v ss >/dev/null 2>&1; then
ss -ltn "sport = :${port}" | grep -q LISTEN
return $?
fi
if command -v lsof >/dev/null 2>&1; then
lsof -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
return $?
fi
netstat -an 2>/dev/null | grep -E "[:.]${port}[[:space:]]" | grep -qi LISTEN
}

resolve_port() {
local label="$1"
local default_port="$2"
local chosen="$default_port"

while is_port_in_use "$chosen"; do
echo "Port ${chosen} is already in use for ${label}."
read -r -p "Enter alternate port for ${label} (blank to exit): " chosen
if [[ -z "$chosen" ]]; then
echo "Exiting. Free port ${default_port} or choose an alternate port next run."
exit 1
fi
if ! [[ "$chosen" =~ ^[0-9]+$ ]] || (( chosen < 1 || chosen > 65535 )); then
echo "Invalid port: ${chosen}"
chosen="$default_port"
fi
done

echo "$chosen"
}

DEVBITS_BACKEND_PORT="$(resolve_port "backend" 8080)"
DEVBITS_DB_PORT="$(resolve_port "postgres" 5433)"
export DEVBITS_BACKEND_PORT
export DEVBITS_DB_PORT

echo "Using backend port ${DEVBITS_BACKEND_PORT} and db port ${DEVBITS_DB_PORT}."

cd "$BACKEND_DIR"
docker compose -p "$COMPOSE_PROJECT" -f docker-compose.dev.yml down --volumes --remove-orphans
docker compose -p "$COMPOSE_PROJECT" -f docker-compose.dev.yml up -d --build

echo "Waiting for database readiness..."
for i in $(seq 1 60); do
if docker compose -p "$COMPOSE_PROJECT" -f docker-compose.dev.yml exec -T db pg_isready -U devbits_dev -d devbits_dev >/dev/null 2>&1; then
echo "Database is ready."
break
fi
if [[ "$i" -eq 60 ]]; then
echo "Error: Database did not become ready within 60 seconds."
exit 1
fi
sleep 1
done

echo "Waiting for backend health check..."
for i in $(seq 1 60); do
if command -v curl >/dev/null 2>&1; then
if curl -fsS "http://localhost:${DEVBITS_BACKEND_PORT}/health" >/dev/null 2>&1; then
echo "Backend is healthy."
break
fi
elif command -v wget >/dev/null 2>&1; then
if wget -q -O /dev/null "http://localhost:${DEVBITS_BACKEND_PORT}/health"; then
echo "Backend is healthy."
break
fi
fi

if [[ "$i" -eq 60 ]]; then
echo "Error: Backend did not become healthy within 60 seconds."
docker compose -p "$COMPOSE_PROJECT" -f docker-compose.dev.yml logs backend --tail 100
exit 1
fi
sleep 1
done

echo "Launching frontend in local backend mode..."
cd "$ROOT"
EXPO_PUBLIC_LOCAL_API_PORT="$DEVBITS_BACKEND_PORT" "$SCRIPT_DIR/run-front.sh" --local $CLEAR_FRONTEND
99 changes: 99 additions & 0 deletions Bash-Scripts/run-front.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env bash

# Script: run-front.sh
# Does: Starts Expo frontend and lets you choose backend target (Production or Local).
# Use: ./run-front.sh [--local|--production] [--clear] [--dev-client]
# DB: None (frontend only).
# Ports: Metro uses LAN IP; local API defaults to :8080 (EXPO_PUBLIC_LOCAL_API_PORT overrides).
# Modes: Frontend=ON | Backend=Production URL or Local URL | Live stack untouched | Dev/Test DB untouched.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
FRONTEND_DIR="$ROOT/frontend"

MODE=""
CLEAR_FLAG=""
DEV_CLIENT=false

for arg in "$@"; do
case "$arg" in
--clear)
CLEAR_FLAG="--clear"
;;
--dev-client)
DEV_CLIENT=true
;;
--local)
MODE="local"
;;
--production)
MODE="production"
;;
*)
echo "Unknown argument: $arg"
echo "Usage: ./run-front.sh [--local|--production] [--clear] [--dev-client]"
exit 1
;;
esac
done

detect_lan_ip() {
hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^10\.|^192\.168\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.' | head -n1
}

LAN_IP="$(detect_lan_ip || true)"
if [[ -z "$LAN_IP" ]]; then
LAN_IP="127.0.0.1"
echo "Warning: Could not detect private LAN IPv4. Falling back to 127.0.0.1."
fi

if [[ -z "$MODE" ]]; then
echo "Select backend: 1) Production (devbits.ddns.net) 2) Local (LAN IP:8080)"
read -r -p "Choose [1/2]: " selection
case "$selection" in
1) MODE="production" ;;
2) MODE="local" ;;
*)
echo "Invalid selection."
exit 1
;;
esac
fi

if [[ "$LAN_IP" != "127.0.0.1" ]]; then
export REACT_NATIVE_PACKAGER_HOSTNAME="$LAN_IP"
export EXPO_PACKAGER_HOSTNAME="$LAN_IP"
else
unset REACT_NATIVE_PACKAGER_HOSTNAME || true
unset EXPO_PACKAGER_HOSTNAME || true
fi
export EXPO_PUBLIC_API_URL="https://devbits.ddns.net"
export EXPO_PUBLIC_API_FALLBACK_URL="https://devbits.ddns.net"

if [[ "$MODE" == "local" ]]; then
LOCAL_API_PORT="${EXPO_PUBLIC_LOCAL_API_PORT:-8080}"
export EXPO_PUBLIC_USE_LOCAL_API=1
export EXPO_PUBLIC_LOCAL_API_URL="http://${LAN_IP}:${LOCAL_API_PORT}"
echo "Using local backend: $EXPO_PUBLIC_LOCAL_API_URL"
else
export EXPO_PUBLIC_USE_LOCAL_API=0
unset EXPO_PUBLIC_LOCAL_API_URL || true
echo "Using production backend: https://devbits.ddns.net"
fi

cd "$FRONTEND_DIR"

EXPO_ARGS=(expo start --host lan)
if [[ "$DEV_CLIENT" == "true" ]]; then
EXPO_ARGS+=(--dev-client)
else
EXPO_ARGS+=(--go)
fi

if [[ -n "$CLEAR_FLAG" ]]; then
EXPO_ARGS+=(--clear)
fi

exec npx "${EXPO_ARGS[@]}"
Loading
Loading