rairo commited on
Commit
7e45697
·
verified ·
1 Parent(s): 835d4ad

Create main.py

Browse files
Files changed (1) hide show
  1. main.py +251 -0
main.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import uuid
4
+ import datetime
5
+ import pandas as pd
6
+ from flask import Flask, request, jsonify
7
+ from flask_cors import CORS
8
+ from werkzeug.utils import secure_filename
9
+ from flask_apscheduler import APScheduler
10
+ import firebase_admin
11
+ from firebase_admin import credentials, db, auth as firebase_auth
12
+ from resend import Emails
13
+
14
+ # === ENV Config ===
15
+ RESEND_API_KEY = os.getenv("RESEND_API_KEY")
16
+ FIREBASE_CRED_JSON = json.loads(os.getenv("FIREBASE"))
17
+ FIREBASE_DB_URL = os.getenv("Firebase_DB")
18
+ ADMIN_EMAILS = ["rairorr@gmail.com", "nharingoshepherd@gmail.com"]
19
+
20
+ # === Firebase Init ===
21
+ cred = credentials.Certificate(FIREBASE_CRED_JSON)
22
+ firebase_admin.initialize_app(cred, {
23
+ "databaseURL": FIREBASE_DB_URL
24
+ })
25
+
26
+ admin_emails = [
27
+ "rairorr@gmail.com",
28
+ "nharingoshepherd@gmail.com"
29
+ ]
30
+
31
+ for email in admin_emails:
32
+ key = email.replace("@", "_").replace(".", "_")
33
+ db.reference(f"admins/{key}").set({
34
+ "email": email,
35
+ "is_admin": True
36
+ })
37
+
38
+ print("✅ Admins added.")
39
+
40
+ # === Flask App Setup ===
41
+ app = Flask(__name__)
42
+ CORS(app)
43
+
44
+ # === APScheduler Setup ===
45
+ class Config(object):
46
+ SCHEDULER_API_ENABLED = True
47
+
48
+ app.config.from_object(Config())
49
+
50
+ scheduler = APScheduler()
51
+ scheduler.init_app(app)
52
+ scheduler.start()
53
+
54
+ # === Resend Init ===
55
+ Emails.api_key = RESEND_API_KEY
56
+
57
+ def send_email(to, subject, html):
58
+ try:
59
+ Emails.send({
60
+ "from": "Admin <admin@resend.dev>",
61
+ "to": to,
62
+ "subject": subject,
63
+ "html": html
64
+ })
65
+ except Exception as e:
66
+ print("Email error:", e)
67
+
68
+ # === Auth Middleware ===
69
+ def verify_token(req):
70
+ token = req.headers.get("Authorization")
71
+ if not token:
72
+ return None
73
+ try:
74
+ decoded = firebase_auth.verify_id_token(token.replace("Bearer ", ""))
75
+ if decoded["email"] not in ADMIN_EMAILS:
76
+ return None
77
+ return decoded
78
+ except:
79
+ return None
80
+
81
+ # === Admin Setup ===
82
+ def setup_admins():
83
+ ref = db.reference("admins")
84
+ for email in ADMIN_EMAILS:
85
+ uid = f"admin_{email.replace('@', '_').replace('.', '_')}"
86
+ ref.child(uid).set({"email": email, "is_admin": True})
87
+ setup_admins()
88
+
89
+ # === Core Functions ===
90
+ def assign_roster(job_id):
91
+ """Assign roster for a job - now a regular function instead of Celery task"""
92
+ try:
93
+ # Get job details
94
+ job_ref = db.reference(f"jobs/{job_id}")
95
+ job_data = job_ref.get()
96
+
97
+ if not job_data:
98
+ print(f"Job {job_id} not found")
99
+ return
100
+
101
+ # Get all members
102
+ members_ref = db.reference("members")
103
+ members = members_ref.get()
104
+
105
+ if not members:
106
+ print("No members found")
107
+ return
108
+
109
+ # Convert members to list
110
+ members_list = list(members.values()) if isinstance(members, dict) else members
111
+
112
+ # Simple rotation logic (you can enhance this)
113
+ current_assignments = job_data.get("assignments", [])
114
+ last_assigned_index = job_data.get("last_assigned_index", -1)
115
+
116
+ # Find next member to assign
117
+ next_index = (last_assigned_index + 1) % len(members_list)
118
+ assigned_member = members_list[next_index]
119
+
120
+ # Create assignment record
121
+ assignment = {
122
+ "member": assigned_member,
123
+ "assigned_at": datetime.datetime.now().isoformat(),
124
+ "assignment_id": str(uuid.uuid4())
125
+ }
126
+
127
+ # Update assignments
128
+ current_assignments.append(assignment)
129
+
130
+ # Update job with new assignment and index
131
+ job_ref.update({
132
+ "assignments": current_assignments,
133
+ "last_assigned_index": next_index,
134
+ "last_updated": datetime.datetime.now().isoformat()
135
+ })
136
+
137
+ print(f"Assigned {assigned_member.get('name', 'Unknown')} to job {job_id}")
138
+
139
+ # Send notification email (optional)
140
+ # send_email(assigned_member.get('email'), "New Assignment", f"You have been assigned to job {job_id}")
141
+
142
+ except Exception as e:
143
+ print(f"Error in assign_roster for job {job_id}: {e}")
144
+
145
+ def check_and_rotate_guards():
146
+ """Periodic function to check jobs and rotate guards"""
147
+ try:
148
+ print("🔍 Checking for guard rotations...")
149
+
150
+ # Get all jobs
151
+ jobs_ref = db.reference("jobs")
152
+ jobs = jobs_ref.get()
153
+
154
+ if not jobs:
155
+ print("No jobs found")
156
+ return
157
+
158
+ # Check each job for rotation needs
159
+ for job_id, job_data in jobs.items():
160
+ # Check if job needs rotation (example logic - customize as needed)
161
+ assignments = job_data.get("assignments", [])
162
+
163
+ if not assignments:
164
+ # If no assignments yet, start the first one
165
+ print(f"Starting first assignment for job {job_id}")
166
+ assign_roster(job_id)
167
+ else:
168
+ # Check if current assignment is expired (example: 8 hours)
169
+ last_assignment = assignments[-1]
170
+ assigned_at = datetime.datetime.fromisoformat(last_assignment["assigned_at"].replace("Z", "+00:00"))
171
+ now = datetime.datetime.now(assigned_at.tzinfo)
172
+
173
+ # Rotate every 8 hours (customize as needed)
174
+ if (now - assigned_at).total_seconds() > 28800: # 8 hours in seconds
175
+ print(f"Rotating guard for job {job_id}")
176
+ assign_roster(job_id)
177
+
178
+ except Exception as e:
179
+ print(f"Error in check_and_rotate_guards: {e}")
180
+
181
+ # === Routes ===
182
+ @app.route("/upload_members", methods=["POST"])
183
+ def upload_members():
184
+ user = verify_token(request)
185
+ if not user:
186
+ return jsonify({"error": "Unauthorized"}), 401
187
+
188
+ file = request.files.get("file")
189
+ if not file:
190
+ return jsonify({"error": "No file uploaded"}), 400
191
+
192
+ df = pd.read_csv(file) if file.filename.endswith(".csv") else pd.read_excel(file)
193
+ members = df.to_dict(orient="records")
194
+
195
+ for member in members:
196
+ member_id = str(uuid.uuid4())
197
+ db.reference(f"members/{member_id}").set(member)
198
+
199
+ return jsonify({"message": "Members uploaded successfully"}), 200
200
+
201
+ @app.route("/add_member", methods=["POST"])
202
+ def add_member():
203
+ user = verify_token(request)
204
+ if not user:
205
+ return jsonify({"error": "Unauthorized"}), 401
206
+
207
+ data = request.json
208
+ member_id = str(uuid.uuid4())
209
+ db.reference(f"members/{member_id}").set(data)
210
+ return jsonify({"message": "Member added"}), 200
211
+
212
+ @app.route("/create_job", methods=["POST"])
213
+ def create_job():
214
+ user = verify_token(request)
215
+ if not user:
216
+ return jsonify({"error": "Unauthorized"}), 401
217
+
218
+ data = request.json
219
+ job_id = str(uuid.uuid4())
220
+ db.reference(f"jobs/{job_id}").set(data)
221
+ return jsonify({"message": "Job created", "job_id": job_id}), 200
222
+
223
+ @app.route("/start_job/<job_id>", methods=["POST"])
224
+ def start_job(job_id):
225
+ user = verify_token(request)
226
+ if not user:
227
+ return jsonify({"error": "Unauthorized"}), 401
228
+
229
+ # Schedule the first assignment immediately
230
+ scheduler.add_job(
231
+ func=assign_roster,
232
+ trigger="date",
233
+ run_date=datetime.datetime.now() + datetime.timedelta(seconds=5),
234
+ args=[job_id],
235
+ id=f"start_{job_id}_{uuid.uuid4().hex[:8]}"
236
+ )
237
+
238
+ return jsonify({"message": f"Roster assignment started for job {job_id}"}), 202
239
+
240
+ # === Schedule periodic guard rotation ===
241
+ # Run every 5 minutes to check for rotations
242
+ scheduler.add_job(
243
+ func=check_and_rotate_guards,
244
+ trigger="interval",
245
+ minutes=5,
246
+ id="guard_rotation_job"
247
+ )
248
+
249
+ # === Run Server ===
250
+ if __name__ == "__main__":
251
+ app.run(debug=True, port=int(os.getenv("PORT", 7860)))