Changeset 527c097
- Timestamp:
- 04/05/26 20:02:04 (3 months ago)
- Branches:
- main
- Parents:
- 95cf703
- Files:
-
- 5 edited
-
db.py (modified) (2 diffs)
-
main.py (modified) (2 diffs)
-
uc_approve.py (modified) (3 diffs)
-
uc_reserve.py (modified) (3 diffs)
-
uc_users.py (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
db.py
r95cf703 r527c097 1 1 import datetime 2 2 import psycopg2 3 from psycopg2 import pool 4 from contextlib import contextmanager 3 5 4 6 DB_CONFIG = { … … 11 13 } 12 14 15 # -- Connection Pool ---------------------------------------------------------- 16 # Reuses database connections instead of opening a new one per query. 17 # SimpleConnectionPool maintains between minconn and maxconn connections. 13 18 19 _pool = None 20 21 22 def _get_pool(): 23 global _pool 24 if _pool is None: 25 _pool = pool.SimpleConnectionPool(minconn=2, maxconn=10, **DB_CONFIG) 26 return _pool 27 28 29 @contextmanager 14 30 def get_connection(): 15 return psycopg2.connect(**DB_CONFIG) 31 """Get a read-only connection from the pool. 32 Autocommit is enabled so SELECTs don't need explicit commit. 33 Connection is returned to the pool on exit.""" 34 p = _get_pool() 35 conn = p.getconn() 36 try: 37 conn.autocommit = True 38 yield conn 39 finally: 40 conn.autocommit = False 41 p.putconn(conn) 42 43 44 @contextmanager 45 def get_transaction(): 46 """Get a connection with explicit transaction management. 47 Commits on successful exit, rolls back on exception. 48 Connection is returned to the pool in both cases.""" 49 p = _get_pool() 50 conn = p.getconn() 51 try: 52 conn.autocommit = False 53 yield conn 54 conn.commit() 55 except Exception: 56 conn.rollback() 57 raise 58 finally: 59 p.putconn(conn) 60 61 62 def close_pool(): 63 """Close all connections in the pool.""" 64 global _pool 65 if _pool is not None: 66 _pool.closeall() 67 _pool = None 68 69 70 # -- UI Helpers --------------------------------------------------------------- 16 71 17 72 -
main.py
r95cf703 r527c097 4 4 import bcrypt 5 5 6 from db import get_connection, pick_from_list6 from db import get_connection, close_pool, pick_from_list 7 7 from uc_browse import browse_resources 8 8 from uc_reserve import make_reservation … … 95 95 except KeyboardInterrupt: 96 96 print("\n\n Goodbye.") 97 finally: 98 close_pool() 97 99 98 100 -
uc_approve.py
r95cf703 r527c097 1 from db import get_connection, pick_from_list, print_table1 from db import get_connection, get_transaction, pick_from_list, print_table 2 2 3 3 … … 120 120 121 121 new_status = "approved" if action == 0 else "rejected" 122 conn = get_connection()123 122 try: 124 with conn.cursor() as cur: 125 cur.execute( 126 """ 127 UPDATE reservations 128 SET status = %s, approved_by = %s 129 WHERE reservation_id = %s AND status = 'pending' 130 RETURNING reservation_id, status 131 """, 132 (new_status, admin_user_id, res["reservation_id"]), 133 ) 134 result = cur.fetchone() 135 conn.commit() 123 with get_transaction() as conn: 124 with conn.cursor() as cur: 125 # For approvals, re-check conflicts within the transaction 126 # to prevent approving overlapping reservations 127 if new_status == "approved": 128 cur.execute( 129 """ 130 SELECT COUNT(*) FROM reservations 131 WHERE resource_id = %s 132 AND reservation_id != %s 133 AND status = 'approved' 134 AND start_time < %s AND end_time > %s 135 """, 136 (res["resource_id"], res["reservation_id"], 137 res["end_time"], res["start_time"]), 138 ) 139 if cur.fetchone()[0] > 0: 140 raise RuntimeError("Conflict: another reservation was approved for this time slot") 141 142 cur.execute( 143 """ 144 UPDATE reservations 145 SET status = %s, approved_by = %s 146 WHERE reservation_id = %s AND status = 'pending' 147 RETURNING reservation_id, status 148 """, 149 (new_status, admin_user_id, res["reservation_id"]), 150 ) 151 result = cur.fetchone() 152 # Transaction committed automatically 136 153 if result: 137 154 print(f"\n Reservation #{result[0]} has been {result[1]}.") … … 139 156 print("\n Reservation was already processed by another administrator.") 140 157 except Exception as e: 141 conn.rollback()158 # Transaction rolled back automatically 142 159 print(f"\n Error: {e}") 143 finally:144 conn.close() -
uc_reserve.py
r95cf703 r527c097 1 1 import datetime 2 2 3 from db import get_connection, pick_from_list, print_table, input_date, input_time3 from db import get_connection, get_transaction, pick_from_list, print_table, input_date, input_time 4 4 5 5 … … 96 96 return 97 97 98 # Step 7: INSERT 99 conn = get_connection() 98 # Step 7: INSERT (transaction: re-check conflicts + insert atomically) 100 99 try: 101 with conn.cursor() as cur: 102 cur.execute( 103 """ 104 INSERT INTO reservations 105 (start_time, end_time, status, purpose, created_at, user_id, resource_id) 106 VALUES (%s, %s, 'pending', %s, CURRENT_TIMESTAMP, %s, %s) 107 RETURNING reservation_id, status, created_at 108 """, 109 (start_dt, end_dt, purpose, user_id, rid), 110 ) 111 result = cur.fetchone() 112 conn.commit() 100 with get_transaction() as conn: 101 with conn.cursor() as cur: 102 # Re-check conflicts inside the transaction to prevent 103 # race conditions (another user inserting between our 104 # check and insert) 105 cur.execute( 106 """ 107 SELECT COUNT(*) FROM reservations 108 WHERE resource_id = %s 109 AND status IN ('approved', 'pending') 110 AND start_time < %s AND end_time > %s 111 """, 112 (rid, end_dt, start_dt), 113 ) 114 if cur.fetchone()[0] > 0: 115 raise RuntimeError("Conflict: another reservation was just created for this time slot") 116 117 cur.execute( 118 """ 119 INSERT INTO reservations 120 (start_time, end_time, status, purpose, created_at, user_id, resource_id) 121 VALUES (%s, %s, 'pending', %s, CURRENT_TIMESTAMP, %s, %s) 122 RETURNING reservation_id, status, created_at 123 """, 124 (start_dt, end_dt, purpose, user_id, rid), 125 ) 126 result = cur.fetchone() 127 # Transaction committed automatically 113 128 print(f"\n Reservation created successfully!") 114 129 print(f" Reservation ID: {result[0]}") … … 117 132 return 118 133 except Exception as e: 119 conn.rollback()134 # Transaction rolled back automatically 120 135 print(f"\n Error creating reservation: {e}") 121 136 return 122 finally:123 conn.close()124 137 125 138 -
uc_users.py
r95cf703 r527c097 3 3 import bcrypt 4 4 5 from db import get_connection, pick_from_list5 from db import get_connection, get_transaction, pick_from_list 6 6 7 7 … … 40 40 return 41 41 42 # Step 3: Check email uniqueness 43 with get_connection() as conn: 44 with conn.cursor() as cur: 45 cur.execute("SELECT COUNT(*) FROM users WHERE email = %s", (email,)) 46 exists = cur.fetchone()[0] 47 if exists > 0: 48 print(f"\n ERROR: A user with email '{email}' already exists.") 49 return 50 51 # Step 4: Enter password 42 # Step 3: Enter password 52 43 password = getpass.getpass(" Initial password: ").strip() 53 44 if not password: … … 59 50 return 60 51 61 # Step 5: Hash and insert52 # Step 4: Hash password 62 53 hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode() 63 54 64 conn = get_connection() 55 # Step 5: Transaction — check email uniqueness + insert atomically 56 # This prevents a race condition where two users register with the 57 # same email simultaneously (one check passes, both insert). 65 58 try: 66 with conn.cursor() as cur: 67 cur.execute( 68 """ 69 INSERT INTO users (first_name, last_name, email, password, type_id) 70 VALUES (%s, %s, %s, %s, %s) 71 RETURNING user_id, first_name, last_name, email 72 """, 73 (first_name, last_name, email, hashed, type_id), 74 ) 75 result = cur.fetchone() 76 conn.commit() 59 with get_transaction() as conn: 60 with conn.cursor() as cur: 61 cur.execute("SELECT COUNT(*) FROM users WHERE email = %s", (email,)) 62 if cur.fetchone()[0] > 0: 63 raise ValueError(f"A user with email '{email}' already exists.") 77 64 78 # Step 6: Confirmation 65 cur.execute( 66 """ 67 INSERT INTO users (first_name, last_name, email, password, type_id) 68 VALUES (%s, %s, %s, %s, %s) 69 RETURNING user_id, first_name, last_name, email 70 """, 71 (first_name, last_name, email, hashed, type_id), 72 ) 73 result = cur.fetchone() 74 75 # Transaction committed automatically — Step 6: Confirmation 79 76 print(f"\n User created successfully!") 80 77 print(f" User ID: {result[0]}") … … 82 79 print(f" Email: {result[3]}") 83 80 print(f" Role: {role_name}") 81 except ValueError as e: 82 print(f"\n ERROR: {e}") 84 83 except Exception as e: 85 conn.rollback()86 84 print(f"\n Error creating user: {e}") 87 finally:88 conn.close()
Note:
See TracChangeset
for help on using the changeset viewer.
