Initial commit: HabitForge - habit tracking application with FastAPI backend, React frontend, and Zepp integration
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
# PostgreSQL backup script for HabitForge
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
BACKUP_DIR="/backups"
|
||||
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||
BACKUP_FILE="${BACKUP_DIR}/habitforge_backup_${TIMESTAMP}.sql.gz"
|
||||
RETENTION_DAYS=7
|
||||
|
||||
# Database settings (from environment)
|
||||
DB_HOST="${POSTGRES_HOST:-db}"
|
||||
DB_PORT="${POSTGRES_PORT:-5432}"
|
||||
DB_NAME="${POSTGRES_DB:-habitforge}"
|
||||
DB_USER="${POSTGRES_USER:-habitforge}"
|
||||
|
||||
# Create backup directory if it doesn't exist
|
||||
mkdir -p "${BACKUP_DIR}"
|
||||
|
||||
echo "Starting backup at $(date)"
|
||||
|
||||
# Perform backup
|
||||
PGPASSWORD="${POSTGRES_PASSWORD}" pg_dump \
|
||||
-h "${DB_HOST}" \
|
||||
-p "${DB_PORT}" \
|
||||
-U "${DB_USER}" \
|
||||
-d "${DB_NAME}" \
|
||||
--format=plain \
|
||||
--no-owner \
|
||||
--no-acl | gzip > "${BACKUP_FILE}"
|
||||
|
||||
# Check if backup was successful
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Backup completed successfully: ${BACKUP_FILE}"
|
||||
|
||||
# Get file size
|
||||
SIZE=$(du -h "${BACKUP_FILE}" | cut -f1)
|
||||
echo "Backup size: ${SIZE}"
|
||||
|
||||
# Remove old backups (older than RETENTION_DAYS)
|
||||
echo "Removing backups older than ${RETENTION_DAYS} days..."
|
||||
find "${BACKUP_DIR}" -name "habitforge_backup_*.sql.gz" -type f -mtime +${RETENTION_DAYS} -delete
|
||||
|
||||
# List remaining backups
|
||||
echo "Current backups:"
|
||||
ls -lh "${BACKUP_DIR}"/habitforge_backup_*.sql.gz 2>/dev/null || echo "No backups found"
|
||||
|
||||
else
|
||||
echo "Backup failed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Backup finished at $(date)"
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to import Zepp health data from export-zepp directory into HabitForge database.
|
||||
Usage: python scripts/import_zepp_data.py [--user-id USER_ID]
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
|
||||
# Add parent directory to path to import backend modules
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
from backend.integrations.import_zepp import main as import_main, USER_ID as DEFAULT_USER_ID
|
||||
from backend.integrations import import_zepp
|
||||
|
||||
def run_import(user_id: int = None):
|
||||
"""Run the Zepp data import."""
|
||||
if user_id:
|
||||
# Override the default user ID
|
||||
import_zepp.USER_ID = user_id
|
||||
print(f"Importing data for user ID: {user_id}")
|
||||
else:
|
||||
print(f"Importing data for default user ID: {DEFAULT_USER_ID}")
|
||||
|
||||
import_main()
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Import Zepp health data into HabitForge")
|
||||
parser.add_argument(
|
||||
"--user-id",
|
||||
type=int,
|
||||
help="User ID to import data for (default: 1)",
|
||||
default=None
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
run_import(args.user_id)
|
||||
@@ -0,0 +1,162 @@
|
||||
"""
|
||||
Migration script from SQLite to PostgreSQL.
|
||||
|
||||
This script migrates data from the existing SQLite database to PostgreSQL.
|
||||
|
||||
Usage:
|
||||
python scripts/migrate_to_postgres.py
|
||||
|
||||
Prerequisites:
|
||||
- PostgreSQL database must be running
|
||||
- DATABASE_URL environment variable must point to PostgreSQL
|
||||
- SQLite database must exist at data/habitforge.db
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
# Add parent directory to path
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from sqlalchemy import create_engine, text
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from backend import models
|
||||
from backend.database import Base, SessionLocal as SQLiteSessionLocal
|
||||
|
||||
# PostgreSQL connection
|
||||
POSTGRES_URL = os.getenv("DATABASE_URL", "postgresql://habitforge:habitforge@localhost:5432/habitforge")
|
||||
|
||||
def migrate_data():
|
||||
"""Migrate data from SQLite to PostgreSQL."""
|
||||
print("Starting migration from SQLite to PostgreSQL...")
|
||||
|
||||
# Create PostgreSQL engine
|
||||
postgres_engine = create_engine(POSTGRES_URL)
|
||||
PostgresSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=postgres_engine)
|
||||
|
||||
# Create tables in PostgreSQL
|
||||
print("Creating tables in PostgreSQL...")
|
||||
Base.metadata.create_all(bind=postgres_engine)
|
||||
|
||||
# Get SQLite session
|
||||
sqlite_session = SQLiteSessionLocal()
|
||||
|
||||
# Get PostgreSQL session
|
||||
postgres_session = PostgresSessionLocal()
|
||||
|
||||
try:
|
||||
# Migrate Users
|
||||
print("Migrating Users...")
|
||||
users = sqlite_session.query(models.User).all()
|
||||
for user in users:
|
||||
new_user = models.User(
|
||||
id=user.id,
|
||||
username=user.username,
|
||||
hashed_password=user.hashed_password
|
||||
)
|
||||
postgres_session.add(new_user)
|
||||
postgres_session.commit()
|
||||
print(f"Migrated {len(users)} users")
|
||||
|
||||
# Migrate Challenges
|
||||
print("Migrating Challenges...")
|
||||
challenges = sqlite_session.query(models.Challenge).all()
|
||||
for challenge in challenges:
|
||||
new_challenge = models.Challenge(
|
||||
id=challenge.id,
|
||||
name=challenge.name,
|
||||
period_type=challenge.period_type,
|
||||
daily_target=challenge.daily_target,
|
||||
start_date=challenge.start_date,
|
||||
end_date=challenge.end_date,
|
||||
display_order=challenge.display_order,
|
||||
user_id=challenge.user_id,
|
||||
icon=challenge.icon,
|
||||
image_url=challenge.image_url,
|
||||
description=challenge.description,
|
||||
unit_type=challenge.unit_type,
|
||||
rest_days=challenge.rest_days,
|
||||
frequency=challenge.frequency
|
||||
)
|
||||
postgres_session.add(new_challenge)
|
||||
postgres_session.commit()
|
||||
print(f"Migrated {len(challenges)} challenges")
|
||||
|
||||
# Migrate Tracking
|
||||
print("Migrating Tracking...")
|
||||
trackings = sqlite_session.query(models.Tracking).all()
|
||||
for tracking in trackings:
|
||||
new_tracking = models.Tracking(
|
||||
id=tracking.id,
|
||||
challenge_id=tracking.challenge_id,
|
||||
date=tracking.date,
|
||||
reps=tracking.reps,
|
||||
completed=tracking.completed,
|
||||
notes=tracking.notes
|
||||
)
|
||||
postgres_session.add(new_tracking)
|
||||
postgres_session.commit()
|
||||
print(f"Migrated {len(trackings)} trackings")
|
||||
|
||||
# Migrate DailyHealthMetrics
|
||||
print("Migrating DailyHealthMetrics...")
|
||||
metrics = sqlite_session.query(models.DailyHealthMetrics).all()
|
||||
for metric in metrics:
|
||||
new_metric = models.DailyHealthMetrics(
|
||||
id=metric.id,
|
||||
user_id=metric.user_id,
|
||||
date=metric.date,
|
||||
step_count=metric.step_count,
|
||||
calories_burned=metric.calories_burned,
|
||||
distance_meters=metric.distance_meters,
|
||||
sleep_duration_minutes=metric.sleep_duration_minutes,
|
||||
deep_sleep_minutes=metric.deep_sleep_minutes,
|
||||
light_sleep_minutes=metric.light_sleep_minutes,
|
||||
rem_sleep_minutes=metric.rem_sleep_minutes,
|
||||
awake_duration_minutes=metric.awake_duration_minutes,
|
||||
avg_heart_rate=metric.avg_heart_rate,
|
||||
min_heart_rate=metric.min_heart_rate,
|
||||
max_heart_rate=metric.max_heart_rate,
|
||||
avg_spo2=metric.avg_spo2,
|
||||
pai_score=metric.pai_score,
|
||||
weight=metric.weight,
|
||||
resting_heart_rate=metric.resting_heart_rate,
|
||||
hrv=metric.hrv
|
||||
)
|
||||
postgres_session.add(new_metric)
|
||||
postgres_session.commit()
|
||||
print(f"Migrated {len(metrics)} daily health metrics")
|
||||
|
||||
print("\nMigration completed successfully!")
|
||||
print(f"Total records migrated:")
|
||||
print(f" - Users: {len(users)}")
|
||||
print(f" - Challenges: {len(challenges)}")
|
||||
print(f" - Trackings: {len(trackings)}")
|
||||
print(f" - DailyHealthMetrics: {len(metrics)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nError during migration: {e}")
|
||||
postgres_session.rollback()
|
||||
raise
|
||||
finally:
|
||||
sqlite_session.close()
|
||||
postgres_session.close()
|
||||
postgres_engine.dispose()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Check if SQLite database exists
|
||||
if not os.path.exists("data/habitforge.db"):
|
||||
print("Error: SQLite database not found at data/habitforge.db")
|
||||
sys.exit(1)
|
||||
|
||||
# Confirm migration
|
||||
print("This will migrate data from SQLite to PostgreSQL.")
|
||||
print(f"PostgreSQL URL: {POSTGRES_URL}")
|
||||
response = input("Do you want to continue? (yes/no): ")
|
||||
|
||||
if response.lower() != "yes":
|
||||
print("Migration cancelled.")
|
||||
sys.exit(0)
|
||||
|
||||
migrate_data()
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
# Production startup script for HabitForge
|
||||
|
||||
set -e
|
||||
|
||||
echo "Starting HabitForge in production mode..."
|
||||
|
||||
# Wait for database to be ready
|
||||
echo "Waiting for database..."
|
||||
while ! pg_isready -h ${POSTGRES_HOST:-db} -U ${POSTGRES_USER:-habitforge} -d ${POSTGRES_DB:-habitforge} > /dev/null 2>&1; do
|
||||
echo "Database is unavailable - sleeping"
|
||||
sleep 1
|
||||
done
|
||||
echo "Database is ready!"
|
||||
|
||||
# Run migrations
|
||||
echo "Running database migrations..."
|
||||
alembic upgrade head
|
||||
|
||||
# Start Gunicorn
|
||||
echo "Starting Gunicorn..."
|
||||
exec gunicorn backend.main:app \
|
||||
--workers ${GUNICORN_WORKERS:-4} \
|
||||
--worker-class uvicorn.workers.UvicornWorker \
|
||||
--bind 0.0.0.0:8000 \
|
||||
--access-logfile - \
|
||||
--error-logfile - \
|
||||
--log-level ${LOG_LEVEL:-info}
|
||||
Reference in New Issue
Block a user