This repository has been archived by the owner on Oct 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
admin.py
384 lines (338 loc) · 16.2 KB
/
admin.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
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
from sanic import response, redirect, Blueprint, exceptions
from email_util import send_missing_propic_message
from random import choice
from room import unconfirm_room_by_order
from config import *
from utils import *
from ext import *
from io import StringIO
from sanic.log import logger
import csv
import time
import json
import math
bp = Blueprint("admin", url_prefix="/manage/admin")
@bp.middleware
async def credentials_check(request: Request):
order = await get_order(request)
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if EXTRA_PRINTS:
logger.info(f"Checking admin credentials of {order.code} with secret {order.secret}")
if not order.isAdmin() : raise exceptions.Forbidden("Birichino :)")
@bp.get('/cache/clear')
async def clear_cache(request, order:Order):
success = await request.app.ctx.om.fill_cache()
if not success: raise exceptions.ServerError("An error occurred while loading the cache")
return redirect(f'/manage/admin')
@bp.get('/loginas/<code>')
async def login_as(request, code, order:Order):
dOrder = await get_order_by_code(request, code, throwException=True)
if(dOrder.isAdmin()):
raise exceptions.Forbidden("You can't login as another admin!")
if EXTRA_PRINTS:
logger.info(f"Swapping login: {order.secret} {order.code} -> {dOrder.secret} {code}")
r = redirect(f'/manage/welcome')
r.cookies['foxo_code_ORG'] = order.code
r.cookies['foxo_secret_ORG'] = order.secret
r.cookies['foxo_code'] = code
r.cookies['foxo_secret'] = dOrder.secret
return r
@bp.get('/room/verify')
async def verify_rooms(request, order:Order):
await clear_cache(request, order)
already_checked, success = await request.app.ctx.om.update_cache()
if not already_checked and success:
orders = filter(lambda x: x.status not in ['c', 'e'] and x.room_id == x.code, request.app.ctx.om.cache.values())
await validate_rooms(request, orders, None)
return redirect(f'/manage/admin')
@bp.get('/room/unconfirm/<code>')
async def unconfirm_room(request, code, order:Order):
dOrder = await get_order_by_code(request, code, throwException=True)
await unconfirm_room_by_order(order=dOrder, throw=True, request=request)
return redirect(f'/manage/nosecount')
@bp.get('/room/autoconfirm')
async def autoconfirm_room(request, order:Order):
await clear_cache(request, order)
orders = request.app.ctx.om.cache.values()
for order in orders:
if(order.code == order.room_id and not order.room_confirmed and len(order.room_members) == order.room_person_no):
logger.info(f"Auto-Confirming room {order.room_id}")
await confirm_room_by_order(order, request)
await clear_cache(request, order)
return redirect(f'/manage/admin')
@bp.get('/room/delete/<code>')
async def delete_room(request, code, order:Order):
await clear_cache(request, order)
dOrder = await get_order_by_code(request, code, throwException=True)
ppl = await get_people_in_room_by_code(request, code)
for p in ppl:
await p.edit_answer('room_id', None)
await p.edit_answer('room_confirmed', "False")
await p.edit_answer('room_name', None)
await p.edit_answer('pending_room', None)
await p.edit_answer('pending_roommates', None)
await p.edit_answer('room_members', None)
await p.edit_answer('room_owner', None)
await p.edit_answer('room_secret', None)
await p.send_answers()
await dOrder.send_answers()
return redirect(f'/manage/nosecount')
@bp.post('/room/rename/<code>')
async def rename_room(request, code, order:Order):
await clear_cache(request, order)
dOrder = await get_order_by_code(request, code, throwException=True)
name = request.form.get('name')
if len(name) > 64 or len(name) < 4:
raise exceptions.BadRequest("Your room name is invalid. Please try another one.")
await dOrder.edit_answer("room_name", name)
await dOrder.send_answers()
return redirect(f'/manage/nosecount')
@bp.get('/room/wizard')
async def room_wizard(request, order:Order):
'''Tries to autofill unconfirmed rooms and other matches together'''
# Clear cache first
await clear_cache(request, order)
#Separate orders which have incomplete rooms and which have no rooms
all_orders = {key:value for key,value in sorted(request.app.ctx.om.cache.items(), key=lambda x: (x[1].room_person_no, len(x[1].room_members)), reverse=True) if (value.status not in ['canceled', 'expired'] and not value.daily and value.bed_in_room != ITEM_VARIATIONS_MAP["bed_in_room"]["bed_in_room_no_room"])}
orders = {key:value for key,value in sorted(all_orders.items(), key=lambda x: x[1].ans('fursona_name')) if not value.room_confirmed}
# Orders with incomplete rooms
incomplete_orders = {key:value for key,value in orders.items() if value.code == value.room_id and (value.room_person_no - len(value.room_members)) > 0}
# Roomless furs
roomless_orders = {key:value for key,value in orders.items() if(not value.room_id and not value.daily and value.bed_in_room != ITEM_VARIATIONS_MAP["bed_in_room"]["bed_in_room_no_room"])}
# Result map
result_map = {}
# Check overflows
room_quota_overflow = {}
for key, value in ITEM_VARIATIONS_MAP['bed_in_room'].items():
if key != "bed_in_room_no_room":
room_quota = get_quota(ITEMS_ID_MAP['bed_in_room'], value)
capacity = ROOM_CAPACITY_MAP[key] if key in ROOM_CAPACITY_MAP else 1
current_quota = len(list(filter(lambda y: y.bed_in_room == value and y.room_owner == True, all_orders.values())))
room_quota_overflow[value] = current_quota - int(room_quota.size / capacity) if room_quota else 0
if DEV_MODE and EXTRA_PRINTS:
print(f"There are {current_quota} of room type {key} out of a total of ({room_quota.size} / {capacity})")
# Init rooms to remove
result_map["void"] = []
# Remove rooms that are over quota
for room_type, overflow_qty in {key:value for key,value in room_quota_overflow.items() if value > 0}.items():
sorted_rooms = sorted(incomplete_orders.values(), key=lambda r: len(r.room_members))
sorted_rooms = [r for r in sorted_rooms if r.bed_in_room == room_type]
for room_to_remove in sorted_rooms[:overflow_qty]:
# Room codes to remove
result_map["void"].append(room_to_remove.code)
# Move room members to the roomless list
for member_code in room_to_remove.room_members:
roomless_orders[member_code] = all_orders[member_code]
del incomplete_orders[room_to_remove.code]
# Fill already existing rooms
for room_order in incomplete_orders.items():
room = room_order[1]
to_add = []
count = room.room_person_no
alreadyPresent = len(room.room_members)
missing_slots = count - alreadyPresent
for _ in range(missing_slots):
compatible_roomates = {key:value for key,value in roomless_orders.items() if value.bed_in_room == room.bed_in_room}
if len(compatible_roomates.items()) == 0: break
# Try picking a roomate that's from the same country and room type
country = room.country.lower()
roomless_by_country = {key:value for key,value in compatible_roomates.items() if value.country.lower() == country}
if len(roomless_by_country.items()) > 0:
code_to_add = list(roomless_by_country.keys())[0]
to_add.append(code_to_add)
del roomless_orders[code_to_add]
else:
# If not, add first roomless there is
code_to_add = list(compatible_roomates.keys())[0]
to_add.append(code_to_add)
del roomless_orders[code_to_add]
result_map[room.code] = {
'type': 'add_existing',
'to_add': to_add,
'count': count,
'previouslyPresent': alreadyPresent
}
generated_counter = 0
# Create additional rooms
while len(roomless_orders.items()) > 0:
room = list(roomless_orders.items())[0][1]
to_add = []
count = room.room_person_no
alreadyPresent = len(room.room_members)
missing_slots = count - alreadyPresent
for _ in range(missing_slots):
compatible_roomates = {key:value for key,value in roomless_orders.items() if value.bed_in_room == room.bed_in_room}
if len(compatible_roomates.items()) == 0: break
# Try picking a roomate that's from the same country and room type
country = room.country.lower()
roomless_by_country = {key:value for key,value in compatible_roomates.items() if value.country.lower() == country}
if len(roomless_by_country.items()) > 0:
code_to_add = list(roomless_by_country.keys())[0]
to_add.append(code_to_add)
del roomless_orders[code_to_add]
else:
# If not, add first roomless there is
code_to_add = list(compatible_roomates.keys())[0]
to_add.append(code_to_add)
del roomless_orders[code_to_add]
generated_counter += 1
result_map[room.code] = {
'type': 'new',
'room_name': f'Generated Room {generated_counter}',
'room_type': room.bed_in_room,
'to_add': to_add,
'count': count,
'previouslyPresent': alreadyPresent
}
result_map["infinite"] = { 'to_add': [] }
result_map = {k: v for k, v in sorted(result_map.items(), key=lambda x: ((x[1]["count"], x[1]["previouslyPresent"]) if("count" in x[1] and "previouslyPresent" in x[1]) else (4316, 0) ))}
tpl = request.app.ctx.tpl.get_template('wizard.html')
return html(tpl.render(order=order, all_orders=all_orders, unconfirmed_orders=orders, data=result_map, jsondata=json.dumps(result_map, skipkeys=True, ensure_ascii=False)))
@bp.post('/room/wizard/submit')
async def submit_from_room_wizard(request:Request, order:Order):
'''Will apply changes to the rooms'''
await clear_cache(request, order)
data = json.loads(request.body)
# Phase 1 - Delete all rooms in void
if 'void' in data:
for room_code in data['void']:
ppl = await get_people_in_room_by_code(request, room_code)
for p in ppl:
await p.edit_answer('room_id', None)
await p.edit_answer('room_confirmed', "False")
await p.edit_answer('room_name', None)
await p.edit_answer('pending_room', None)
await p.edit_answer('pending_roommates', None)
await p.edit_answer('room_members', None)
await p.edit_answer('room_owner', None)
await p.edit_answer('room_secret', None)
await p.send_answers()
logger.info(f"Deleted rooms {', '.join(data['void'])}")
# Phase 2 - Join roomless to other rooms or add new rooms
for room_code, value in {key:value for key,value in data.items() if key.lower() not in ['void', 'infinite']}.items():
if not value['to_add'] or len(value['to_add']) == 0: continue
room_order = await request.app.ctx.om.get_order(code=room_code)
# Preconditions
if not room_order: raise exceptions.BadRequest(f"Order {room_code} does not exist.")
if room_order.daily == True: raise exceptions.BadRequest(f"Order {room_code} is daily.")
if room_order.status != 'paid': raise exceptions.BadRequest(f"Order {room_code} hasn't paid.")
if room_order.room_owner:
if room_order.room_person_no < len(room_order.room_members) + (len(value['to_add']) if value['to_add'] else 0):
raise exceptions.BadRequest(f"Input exceeds room {room_order.code} capacity.")
elif room_order.room_person_no < (len(value['to_add']) if value['to_add'] else 0):
raise exceptions.BadRequest(f"Input exceeds room {room_order.code} capacity.")
# Adding roomless orders to existing rooms
if value['type'] == 'add_existing' or value['type'] == 'new':
if value['type'] == 'new':
if room_order.room_owner: exceptions.BadRequest(f"Order {room_code} is already a room owner.")
# Create room data
await room_order.edit_answer('room_name', value['room_name'])
await room_order.edit_answer('room_id', room_order.code)
await room_order.edit_answer('room_secret', ''.join(choice('0123456789') for _ in range(6)))
elif not room_order.room_owner:
raise exceptions.BadRequest(f"Order {room_code} is not a room owner.")
# Add members
for new_member_code in value['to_add']:
pending_member = await request.app.ctx.om.get_order(code=new_member_code)
# Preconditions
if pending_member.daily == True: raise exceptions.BadRequest(f"Order {pending_member.code} is daily.")
#if pending_member.status != 'paid': raise exceptions.BadRequest(f"Order {new_member_code} hasn't paid.") # Since we don't confirm rooms anymore, we don't need to check if they're paid or not
if pending_member.bed_in_room != room_order.bed_in_room: raise exceptions.BadRequest(f"Order {new_member_code} has a different room type than {room_code}.")
if pending_member.room_owner: exceptions.BadRequest(f"Order {new_member_code} is already a room owner.")
if pending_member.room_id and pending_member.room_id not in data['void']: exceptions.BadRequest(f"Order {new_member_code} is in another room.")
await pending_member.edit_answer('room_id', room_order.code)
await pending_member.edit_answer('room_confirmed', "True")
await pending_member.edit_answer('pending_room', None)
await pending_member.send_answers()
logger.info(f"{'Created' if value['type'] == 'new' else 'Edited'} {str(room_order)}")
# Confirm members that were already inside the room
if value['type'] == 'add_existing':
for already_member in list(filter(lambda rm: rm.code in room_order.room_members and rm.code != room_order.code, request.app.ctx.om.cache.values())):
await already_member.edit_answer('room_confirmed', "True")
await already_member.send_answers()
else: raise exceptions.BadRequest(f"Unexpected type ({value['type']})")
await room_order.edit_answer('pending_room', None)
await room_order.edit_answer('pending_roommates', None)
# await room_order.edit_answer('room_confirmed', "True") Use the autoconfirm button in the admin panel
await room_order.edit_answer('room_members', ','.join(list(set([*room_order.room_members, room_order.code, *value['to_add']]))))
await room_order.send_answers()
await clear_cache(request, order)
return text('done', status=200)
@bp.get('/propic/remind')
async def propic_remind_missing(request, order:Order):
await clear_cache(request, order)
orders = request.app.ctx.om.cache.values()
order: Order
for order in orders:
missingPropic = order.propic is None
missingFursuitPropic = order.is_fursuiter and order.propic_fursuiter is None
if(missingPropic or missingFursuitPropic):
# print(f"{order.code}: prp={missingPropic} fpr={missingFursuitPropic} - {order.name}")
await send_missing_propic_message(order, missingPropic, missingFursuitPropic)
return redirect(f'/manage/admin')
@bp.get('/export/export')
async def export_export(request, order:Order):
await clear_cache(request, order)
data = StringIO()
w = csv.writer(data)
w.writerow(['Status', 'Code', 'First name', 'Last name', 'Nick', 'State', 'Card', 'Artist', 'Fursuiter', 'Sponsorhip', 'Early', 'Late', 'Daily', 'Daily days', 'Shirt', 'Room type', 'Room count', 'Room members', 'Payment', 'Price', 'Refunds', 'Staff'])
orders = request.app.ctx.om.cache.values()
order: Order
for order in orders:
w.writerow([
order.status,
order.code,
order.first_name,
order.last_name,
order.name,
order.country,
order.has_card,
order.is_artist,
order.is_fursuiter,
order.sponsorship,
order.has_early,
order.has_late,
order.daily,
','.join(order.dailyDays),
order.shirt_size,
ROOM_TYPE_NAMES[order.bed_in_room] if order.bed_in_room in ROOM_TYPE_NAMES else "-",
len(order.room_members),
','.join(order.room_members),
order.payment_provider,
order.total - order.fees,
order.refunds,
order.ans('staff_role') or 'attendee',
])
data.seek(0)
str = data.read() #data.read().decode("UTF-8")
data.flush()
data.close()
return raw(str, status=200, headers={'Content-Disposition': f'attachment; filename="export_{int(time.time())}.csv"', "Content-Type": "text/csv; charset=UTF-8"})
@bp.get('/export/hotel')
async def export_hotel(request, order:Order):
await clear_cache(request, order)
data = StringIO()
w = csv.writer(data)
w.writerow(['Room type', 'Room name', 'Room code', 'First name', 'Last name', 'Birthday', 'Address', 'Email', 'Phone number', 'Status'])
orders = sorted(request.app.ctx.om.cache.values(), key=lambda d: (d.room_id if d.room_id != None else "~"))
order: Order
for order in orders:
w.writerow([
ROOM_TYPE_NAMES[order.bed_in_room] if order.bed_in_room in ROOM_TYPE_NAMES else "-",
order.room_name,
order.room_id,
order.first_name,
order.last_name,
order.birth_date,
order.address,
order.email,
order.phone,
order.status,
order.code
])
data.seek(0)
str = data.read() #data.read().decode("UTF-8")
data.flush()
data.close()
return raw(str, status=200, headers={'Content-Disposition': f'attachment; filename="hotel_{int(time.time())}.csv"', "Content-Type": "text/csv; charset=UTF-8"})