-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.py
334 lines (287 loc) · 9.63 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
from fasthtml.common import *
import random
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
app = FastHTML(
hdrs=picolink,
debug=True
)
db = database('data/santa.db')
def navbar():
return Nav(
Div(
A("Home", href='/'),
A("See Past Assignments", href='/assignments'),
A("New Assignment", href='/new-assignment'),
A("Send Emails", href='/send-email')
)
)
@app.get("/")
def get_homepage():
return Titled(
"Casias Secret Santa",
navbar(),
Card(
Div(
P("Welcome to the Casias family Secret Santa site!", style='text-align: center')
)
)
)
def assignment_table(gifter:str|None = None, giftee:str|None = None, year:int|None = None) -> Table:
query = "SELECT * FROM assignments"
conditions = []
params = []
if gifter:
conditions.append("gifter like ?")
params.append(gifter)
if giftee:
conditions.append("giftee like ?")
params.append(giftee)
if year:
conditions.append("year = ?")
params.append(year)
if conditions:
query += " WHERE " + " AND ".join(conditions)
results = db.query(query, params)
rows = [
Tr(
*[Td(value) for value in result.values()]
)
for result in results
]
head = Thead(
*[Th(column) for column in ("gifter", "giftee", "year")]
)
return Table(
head,
*rows,
style='margin-left: auto; margin-right:auto'
)
@app.get("/assignments")
def get_assignments(gifter:str|None=None, giftee:str|None=None, year:int|None=None):
return Titled(
"Past Secret Santa Assignments",
navbar(),
Card(
Form(
Input(id='gifter', placeholder='gifter'),
Input(id='giftee', placeholder='giftee'),
Input(id='year', placeholder='year'),
Button('submit'),
action=f'/assignments', method='get')
),
assignment_table(gifter, giftee, year)
)
@app.get("/new-assignment")
def get_new_assignment():
return Titled(
"Create New Secret Santa Assignment",
navbar(),
Card(
Form(
Input(id="year", placeholder="Enter year", type="number", name="year", required=True),
Button("Create Assignments", type="submit"),
action="/new-assignment", method="post"
)
)
)
@app.post("/new-assignment")
def post_new_assignment(year: int):
# Fetch all users and their restrictions
users = db.query("SELECT id, name FROM Users")
# Combine restrictions:
# 1. Permanent and current restrictions from UserRestrictions (year IS NULL or current year)
# 2. Last year's assignments from SecretSantaAssignments
restrictions = db.query(
"""
SELECT user_id, restricted_user_id
from (
SELECT user_id, restricted_user_id
FROM UserRestrictions
WHERE year IS NULL OR year = ?
union
SELECT gifter_id AS user_id, giftee_id AS restricted_user_id
FROM SecretSantaAssignments
WHERE year = ?
)
""",
(year, year - 1,)
)
# Prepare data structures for assignment
user_ids = [user["id"] for user in users]
random.shuffle(user_ids)
restricted = {}
for r in restrictions:
user_id = r["user_id"]
restricted_user_id = r["restricted_user_id"]
if user_id not in restricted:
restricted[user_id] = []
restricted[user_id].append(restricted_user_id)
# Generate valid assignments
assignments = {}
attempts = 0
max_attempts = 1000 # Limit retries
while attempts < max_attempts:
random.shuffle(user_ids)
if is_valid_assignment(user_ids, restricted):
assignments = {gifter: giftee for gifter, giftee in enumerate(user_ids)}
break
attempts += 1
if not assignments:
return Titled(
"Create New Secret Santa Assignment",
navbar(),
Card(P("Failed to generate valid assignments after multiple attempts."))
)
# Save assignments in the database
for gifter_id, giftee_id in assignments.items():
db.t.SecretSantaAssignments.insert(giftee_id=giftee_id, gifter_id=gifter_id, year=year)
return Titled(
"Create New Secret Santa Assignment",
navbar(),
Card(P(f"Assignments created successfully for the year {year}!"))
)
@app.get("/send-email")
def get_send_email():
default_body = """
Hi {gifter},
You are the Secret Santa for {giftee} this year!
Happy gifting!
"""
return Titled(
"Send Secret Santa Emails",
navbar(),
Card(
P(
"Sample email body:"
),
P(
"Casias Family Christmas {year}"
),
P(
default_body
)
),
Card(
Form(
Input(id="year", placeholder="Enter year", type="number", name="year", required=True),
Textarea(
id="email_subject",
name="email_subject",
placeholder="Enter email subject here",
required=True,
value="Casias Family Christmas {year}"
),
Textarea(
id="email_body",
name="email_body",
placeholder="Enter email body here",
required=True,
value=default_body,
style="width: 100%; height: 200px;"
),
Div(
Label("Dry Run:"),
Input(id="dry_run", name="dry_run", type="checkbox", checked=True),
style="margin-top: 10px;"
),
Button("Send Emails", type="submit"),
action="/send-email", method="post"
)
)
)
@app.post("/send-email")
def post_send_email(year: int, email_subject:str, email_body: str, dry_run: bool = False):
# Fetch assignments for the specified year
assignments = db.query(
"""
SELECT
gifter.id as gifter_id, gifter.name as gifter_name, gifter.email as gifter_email,
giftee.name as giftee_name
FROM SecretSantaAssignments
JOIN Users gifter ON SecretSantaAssignments.gifter_id = gifter.id
JOIN Users giftee ON SecretSantaAssignments.giftee_id = giftee.id
WHERE year = ?
""",
(year,)
)
if not assignments:
return Titled(
"Send Secret Santa Emails",
navbar(),
Card(P(f"No assignments found for the year {year}."))
)
# Dry run: Send a single email to the sender with all assignments
if dry_run:
with open("santaDataSecrets", "r") as secret:
secrets = dict(line.strip().split("=") for line in secret)
sender_email = secrets["email"]
subject = "Secret Santa Test {year}".format(year=year)
body = "Secret Santa Assignments:\n\n"
for assignment in assignments:
body += f"{assignment['gifter_name']} -> {assignment['giftee_name']}\n"
send_email(
sender_email, # To sender_email
"Santa Admin", # Gifter (Admin)
"Santa Assignments", # Giftee (Placeholder)
year,
body,
subject
)
return Titled(
"Send Secret Santa Emails",
navbar(),
Card(P("Dry run successful! All assignments sent to the sender's email."))
)
else:
# Regular email sending
customized_subject = email_subject.format(year=year)
for assignment in assignments:
customized_body = email_body.format(
gifter=assignment["gifter_name"],
giftee=assignment["giftee_name"]
)
send_email(
assignment["gifter_email"],
assignment["gifter_name"],
assignment["giftee_name"],
year,
customized_body,
customized_subject
)
return Titled(
"Send Secret Santa Emails",
navbar(),
Card(P(f"Emails sent successfully for the year {year}!"))
)
def is_valid_assignment(user_ids, restricted):
for gifter_id, giftee_id in enumerate(user_ids):
if giftee_id == gifter_id or giftee_id in restricted.get(gifter_id):
return False
return True
def send_email(email, gifter, giftee, year, email_body, email_subject):
# Load email credentials
with open("santaDataSecrets", "r") as secret:
secrets = dict(line.strip().split("=") for line in secret)
sender_email = secrets["email"]
sender_password = secrets["password"]
# Configure SMTP
server = smtplib.SMTP("smtp.gmail.com", 587)
server.starttls()
server.login(sender_email, sender_password)
# Create email
subject = email_subject
body = email_body # Use the custom body passed to the function
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = email
message["Subject"] = subject
message.attach(MIMEText(body, "plain"))
# Send email
server.sendmail(sender_email, email, message.as_string())
server.quit()
@app.get("/favicon.ico")
def get_favicon():
return FileResponse('./static/favicon.png')
serve()