Michael-Antony commited on
Commit
d00a415
Β·
1 Parent(s): 1477fa7

feat: add migration management tools

Browse files

- Created run_all_migrations.py to run all migrations in order
- Created check_migrations.py to verify migration status
- Updated setup.sh to use new migration runner
- Shows progress, summary, and troubleshooting tips
- Checks TimescaleDB extension and hypertable status
- Lists missing migrations with instructions

Files changed (3) hide show
  1. check_migrations.py +180 -0
  2. run_all_migrations.py +157 -0
  3. setup.sh +12 -6
check_migrations.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Check which migrations have been run and which tables exist.
4
+ """
5
+
6
+ import asyncio
7
+ import asyncpg
8
+ from dotenv import load_dotenv
9
+ import os
10
+
11
+ # Load environment variables
12
+ load_dotenv()
13
+
14
+ # Database connection details
15
+ DB_USER = os.getenv("DB_USER")
16
+ DB_PASSWORD = os.getenv("DB_PASSWORD")
17
+ DB_HOST = os.getenv("DB_HOST")
18
+ DB_PORT = os.getenv("DB_PORT", "5432")
19
+ DB_NAME = os.getenv("DB_NAME")
20
+
21
+
22
+ async def check_migrations():
23
+ """Check migration status"""
24
+
25
+ conn_string = f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}?sslmode=require"
26
+
27
+ print("="*70)
28
+ print("πŸ” MIGRATION STATUS CHECK")
29
+ print("="*70)
30
+ print()
31
+
32
+ try:
33
+ conn = await asyncpg.connect(conn_string)
34
+ print("βœ… Connected to database")
35
+ print(f" Host: {DB_HOST}")
36
+ print(f" Database: {DB_NAME}\n")
37
+
38
+ # Check trans schema
39
+ print("πŸ“ Checking trans schema...")
40
+ schema_exists = await conn.fetchval("""
41
+ SELECT EXISTS(
42
+ SELECT 1 FROM information_schema.schemata
43
+ WHERE schema_name = 'trans'
44
+ )
45
+ """)
46
+
47
+ if schema_exists:
48
+ print(" βœ… trans schema exists\n")
49
+ else:
50
+ print(" ❌ trans schema does not exist\n")
51
+ await conn.close()
52
+ return
53
+
54
+ # Check TimescaleDB extension
55
+ print("πŸ”§ Checking TimescaleDB extension...")
56
+ timescale = await conn.fetchrow("""
57
+ SELECT extname, extversion
58
+ FROM pg_extension
59
+ WHERE extname = 'timescaledb'
60
+ """)
61
+
62
+ if timescale:
63
+ print(f" βœ… TimescaleDB {timescale['extversion']} installed\n")
64
+ else:
65
+ print(" ⚠️ TimescaleDB not installed (required for location tracking)\n")
66
+
67
+ # Define expected tables
68
+ tables = [
69
+ ("scm_attendance", "Attendance check-in/out records"),
70
+ ("scm_tasks", "Task assignments"),
71
+ ("scm_task_attachments", "Task photos and signatures"),
72
+ ("scm_location_points", "GPS location tracking (TimescaleDB)"),
73
+ ("scm_geofences", "Work zone definitions"),
74
+ ]
75
+
76
+ print("πŸ“‹ Checking tables...")
77
+ print()
78
+
79
+ results = []
80
+
81
+ for table_name, description in tables:
82
+ # Check if table exists
83
+ exists = await conn.fetchval("""
84
+ SELECT EXISTS(
85
+ SELECT 1 FROM information_schema.tables
86
+ WHERE table_schema = 'trans'
87
+ AND table_name = $1
88
+ )
89
+ """, table_name)
90
+
91
+ if exists:
92
+ # Get row count
93
+ count = await conn.fetchval(f"SELECT COUNT(*) FROM trans.{table_name}")
94
+
95
+ # Check if it's a hypertable
96
+ is_hypertable = False
97
+ if table_name == "scm_location_points" and timescale:
98
+ is_hypertable = await conn.fetchval("""
99
+ SELECT EXISTS(
100
+ SELECT 1 FROM timescaledb_information.hypertables
101
+ WHERE hypertable_name = $1
102
+ )
103
+ """, table_name)
104
+
105
+ status = "βœ…"
106
+ extra = f" ({count} rows)"
107
+ if is_hypertable:
108
+ extra += " [HYPERTABLE]"
109
+
110
+ results.append((table_name, True, count, is_hypertable))
111
+ else:
112
+ status = "❌"
113
+ extra = " (not created)"
114
+ results.append((table_name, False, 0, False))
115
+
116
+ print(f" {status} {table_name:<25} {description}{extra}")
117
+
118
+ print()
119
+
120
+ # Summary
121
+ created_count = sum(1 for _, exists, _, _ in results if exists)
122
+ total_count = len(results)
123
+
124
+ print("="*70)
125
+ print("πŸ“Š SUMMARY")
126
+ print("="*70)
127
+ print()
128
+ print(f"Tables created: {created_count}/{total_count}")
129
+ print()
130
+
131
+ if created_count == total_count:
132
+ print("βœ… All migrations have been run!")
133
+ print()
134
+ print("πŸ“š You can now:")
135
+ print(" 1. Start server: python3 -m uvicorn app.main:app --port 8003 --reload")
136
+ print(" 2. Test APIs: python3 test_tasks_api.py")
137
+ print(" 3. View docs: http://localhost:8003/docs")
138
+ elif created_count == 0:
139
+ print("❌ No migrations have been run yet!")
140
+ print()
141
+ print("πŸš€ Run migrations:")
142
+ print(" python3 run_all_migrations.py")
143
+ else:
144
+ print("⚠️ Some migrations are missing!")
145
+ print()
146
+ print("Missing tables:")
147
+ for table_name, exists, _, _ in results:
148
+ if not exists:
149
+ print(f" ❌ {table_name}")
150
+ print()
151
+ print("πŸš€ Run missing migrations:")
152
+ if not results[0][1]: # scm_attendance
153
+ print(" python3 migrate_attendance.py")
154
+ if not results[1][1]: # scm_tasks
155
+ print(" python3 migrate_tasks.py")
156
+ if not results[2][1]: # scm_task_attachments
157
+ print(" python3 migrate_task_attachments.py")
158
+ if not results[3][1]: # scm_location_points
159
+ print(" python3 migrate_location_tracking.py")
160
+ if not results[4][1]: # scm_geofences
161
+ print(" python3 migrate_geofences.py")
162
+ print()
163
+ print("Or run all at once:")
164
+ print(" python3 run_all_migrations.py")
165
+
166
+ print()
167
+
168
+ await conn.close()
169
+
170
+ except Exception as e:
171
+ print(f"❌ Error: {e}")
172
+ print()
173
+ print("πŸ”§ Troubleshooting:")
174
+ print(" 1. Check .env file has correct database credentials")
175
+ print(" 2. Verify database is accessible")
176
+ print(" 3. Check network connection")
177
+
178
+
179
+ if __name__ == "__main__":
180
+ asyncio.run(check_migrations())
run_all_migrations.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Run all database migrations in the correct order.
4
+ This script should be run once during initial setup or when deploying updates.
5
+ """
6
+
7
+ import asyncio
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ # Add current directory to path
12
+ sys.path.insert(0, str(Path(__file__).parent))
13
+
14
+
15
+ async def run_migration(migration_name: str, migration_func):
16
+ """Run a single migration with error handling"""
17
+ print("\n" + "="*70)
18
+ print(f"πŸš€ Running: {migration_name}")
19
+ print("="*70)
20
+
21
+ try:
22
+ success = await migration_func()
23
+ if success:
24
+ print(f"βœ… {migration_name} completed successfully")
25
+ return True
26
+ else:
27
+ print(f"❌ {migration_name} failed")
28
+ return False
29
+ except Exception as e:
30
+ print(f"❌ {migration_name} failed with error: {e}")
31
+ return False
32
+
33
+
34
+ async def main():
35
+ """Run all migrations in order"""
36
+
37
+ print("\n" + "="*70)
38
+ print("πŸš€ TRACKER MICROSERVICE - DATABASE MIGRATIONS")
39
+ print("="*70)
40
+ print("\nThis will create/update all database tables:")
41
+ print(" 1. trans.scm_attendance")
42
+ print(" 2. trans.scm_tasks")
43
+ print(" 3. trans.scm_task_attachments")
44
+ print(" 4. trans.scm_location_points (TimescaleDB hypertable)")
45
+ print(" 5. trans.scm_geofences")
46
+ print()
47
+
48
+ response = input("Continue? (y/n): ")
49
+ if response.lower() not in ['y', 'yes']:
50
+ print("❌ Migration cancelled")
51
+ return False
52
+
53
+ results = []
54
+
55
+ # Migration 1: Attendance
56
+ print("\n" + "="*70)
57
+ print("πŸ“‹ MIGRATION 1/5: Attendance Table")
58
+ print("="*70)
59
+ try:
60
+ from migrate_attendance import migrate as migrate_attendance
61
+ success = await run_migration("Attendance Migration", migrate_attendance)
62
+ results.append(("Attendance", success))
63
+ except Exception as e:
64
+ print(f"❌ Failed to import/run attendance migration: {e}")
65
+ results.append(("Attendance", False))
66
+
67
+ # Migration 2: Tasks
68
+ print("\n" + "="*70)
69
+ print("πŸ“‹ MIGRATION 2/5: Tasks Table")
70
+ print("="*70)
71
+ try:
72
+ from migrate_tasks import migrate as migrate_tasks
73
+ success = await run_migration("Tasks Migration", migrate_tasks)
74
+ results.append(("Tasks", success))
75
+ except Exception as e:
76
+ print(f"❌ Failed to import/run tasks migration: {e}")
77
+ results.append(("Tasks", False))
78
+
79
+ # Migration 3: Task Attachments
80
+ print("\n" + "="*70)
81
+ print("πŸ“‹ MIGRATION 3/5: Task Attachments Table")
82
+ print("="*70)
83
+ try:
84
+ from migrate_task_attachments import migrate as migrate_attachments
85
+ success = await run_migration("Task Attachments Migration", migrate_attachments)
86
+ results.append(("Task Attachments", success))
87
+ except Exception as e:
88
+ print(f"❌ Failed to import/run task attachments migration: {e}")
89
+ results.append(("Task Attachments", False))
90
+
91
+ # Migration 4: Location Tracking (TimescaleDB)
92
+ print("\n" + "="*70)
93
+ print("πŸ“‹ MIGRATION 4/5: Location Tracking (TimescaleDB Hypertable)")
94
+ print("="*70)
95
+ try:
96
+ from migrate_location_tracking import migrate as migrate_location
97
+ success = await run_migration("Location Tracking Migration", migrate_location)
98
+ results.append(("Location Tracking", success))
99
+ except Exception as e:
100
+ print(f"❌ Failed to import/run location tracking migration: {e}")
101
+ results.append(("Location Tracking", False))
102
+
103
+ # Migration 5: Geofences
104
+ print("\n" + "="*70)
105
+ print("πŸ“‹ MIGRATION 5/5: Geofences Table")
106
+ print("="*70)
107
+ try:
108
+ from migrate_geofences import migrate as migrate_geofences
109
+ success = await run_migration("Geofences Migration", migrate_geofences)
110
+ results.append(("Geofences", success))
111
+ except Exception as e:
112
+ print(f"❌ Failed to import/run geofences migration: {e}")
113
+ results.append(("Geofences", False))
114
+
115
+ # Summary
116
+ print("\n" + "="*70)
117
+ print("πŸ“Š MIGRATION SUMMARY")
118
+ print("="*70)
119
+ print()
120
+
121
+ success_count = sum(1 for _, success in results if success)
122
+ total_count = len(results)
123
+
124
+ for name, success in results:
125
+ status = "βœ…" if success else "❌"
126
+ print(f"{status} {name}")
127
+
128
+ print()
129
+ print(f"Completed: {success_count}/{total_count}")
130
+
131
+ if success_count == total_count:
132
+ print("\nπŸŽ‰ All migrations completed successfully!")
133
+ print("\nπŸ“š Next steps:")
134
+ print(" 1. Start server: python3 -m uvicorn app.main:app --port 8003 --reload")
135
+ print(" 2. Test APIs: python3 test_tasks_api.py")
136
+ print(" 3. View docs: http://localhost:8003/docs")
137
+ return True
138
+ else:
139
+ print("\n⚠️ Some migrations failed. Please check the errors above.")
140
+ print("\nπŸ”§ Troubleshooting:")
141
+ print(" 1. Check .env file has correct database credentials")
142
+ print(" 2. Verify database connection")
143
+ print(" 3. Check if TimescaleDB extension is installed")
144
+ print(" 4. Run individual migrations to see detailed errors")
145
+ return False
146
+
147
+
148
+ if __name__ == "__main__":
149
+ try:
150
+ success = asyncio.run(main())
151
+ exit(0 if success else 1)
152
+ except KeyboardInterrupt:
153
+ print("\n\n❌ Migration cancelled by user")
154
+ exit(1)
155
+ except Exception as e:
156
+ print(f"\n\n❌ Unexpected error: {e}")
157
+ exit(1)
setup.sh CHANGED
@@ -29,15 +29,15 @@ else
29
  echo "βœ… Created .env file. Please edit it with your credentials."
30
  fi
31
 
32
- # Run migration
33
  echo ""
34
- echo "Do you want to run the database migration now? (y/n)"
35
  read -r response
36
  if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
37
- echo "Running migration..."
38
- python3 migrate_attendance.py
39
  else
40
- echo "Skipping migration. Run 'python3 migrate_attendance.py' when ready."
41
  fi
42
 
43
  echo ""
@@ -47,7 +47,13 @@ echo "=========================================="
47
  echo ""
48
  echo "Next steps:"
49
  echo "1. Edit .env file with your database credentials"
50
- echo "2. Run migration: python3 migrate_attendance.py"
 
 
 
 
 
 
51
  echo "3. Start service: python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8003 --reload"
52
  echo "4. Or press F5 in VS Code to debug"
53
  echo ""
 
29
  echo "βœ… Created .env file. Please edit it with your credentials."
30
  fi
31
 
32
+ # Run migrations
33
  echo ""
34
+ echo "Do you want to run all database migrations now? (y/n)"
35
  read -r response
36
  if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
37
+ echo "Running all migrations..."
38
+ python3 run_all_migrations.py
39
  else
40
+ echo "Skipping migrations. Run 'python3 run_all_migrations.py' when ready."
41
  fi
42
 
43
  echo ""
 
47
  echo ""
48
  echo "Next steps:"
49
  echo "1. Edit .env file with your database credentials"
50
+ echo "2. Run all migrations: python3 run_all_migrations.py"
51
+ echo " Or run individually:"
52
+ echo " - python3 migrate_attendance.py"
53
+ echo " - python3 migrate_tasks.py"
54
+ echo " - python3 migrate_task_attachments.py"
55
+ echo " - python3 migrate_location_tracking.py"
56
+ echo " - python3 migrate_geofences.py"
57
  echo "3. Start service: python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8003 --reload"
58
  echo "4. Or press F5 in VS Code to debug"
59
  echo ""