{{title}}
-{{body}}
-diff --git a/admin.py b/admin.py
index 25362d4..63f3b4f 100644
--- a/admin.py
+++ b/admin.py
@@ -3,39 +3,34 @@ from room import unconfirm_room_by_order
from config import *
from utils import *
from ext import *
-import sqlite3
-import smtplib
-import random
-import string
-import httpx
-import json
+from sanic.log import logger
bp = Blueprint("admin", url_prefix="/manage/admin")
-def credentials_check(request, order:Order):
+@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:
- print(f"Checking admin credentials of {order.code} with secret {order.secret}")
+ 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):
- credentials_check(request, order)
await request.app.ctx.om.fill_cache()
return redirect(f'/manage/admin')
@bp.get('/loginas/')
async def login_as(request, code, order:Order):
- credentials_check(request, 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:
- print(f"Swapping login: {order.secret} {order.code} -> {dOrder.secret} {code}")
+ 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
@@ -43,19 +38,25 @@ async def login_as(request, code, order:Order):
r.cookies['foxo_secret'] = dOrder.secret
return r
+@bp.get('/room/verify')
+async def verify_rooms(request, order:Order):
+ already_checked = await request.app.ctx.om.update_cache()
+ if not already_checked:
+ 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/
')
async def unconfirm_room(request, code, order:Order):
- credentials_check(request, order)
dOrder = await get_order_by_code(request, code, throwException=True)
- unconfirm_room_by_order(dOrder, True, request)
+ await unconfirm_room_by_order(order=dOrder, throw=True, request=request)
return redirect(f'/manage/nosecount')
@bp.get('/room/delete/
')
async def delete_room(request, code, order:Order):
- credentials_check(request, order)
dOrder = await get_order_by_code(request, code, throwException=True)
- ppl = get_people_in_room_by_code(request, code)
+ 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")
@@ -72,7 +73,6 @@ async def delete_room(request, code, order:Order):
@bp.post('/room/rename/
')
async def rename_room(request, code, order:Order):
- credentials_check(request, order)
dOrder = await get_order_by_code(request, code, throwException=True)
name = request.form.get('name')
diff --git a/api.py b/api.py
index 22e2163..2e13108 100644
--- a/api.py
+++ b/api.py
@@ -9,6 +9,7 @@ import random
import string
import httpx
import json
+from sanic.log import logger
bp = Blueprint("api", url_prefix="/manage/api")
@@ -185,7 +186,8 @@ async def nfc_scan(request, nfc_id):
@bp.get("/get_token/
/
"
- for err in room_errors:
+ for err in room_order.room_errors:
if err in ROOM_ERROR_TYPES.keys():
- issues_plain += f"{ROOM_ERROR_TYPES[err]}"
+ issues_plain += f" • {ROOM_ERROR_TYPES[err]}\n"
issues_html += f"
"
for member in orders:
plain_body = ROOM_UNCONFIRM_TEXT['plain'].format(member.name, room_order.room_name, issues_plain)
- html_body = ROOM_UNCONFIRM_TEXT['html'].format(member.name, room_order.room_name, issues_html)
+ html_body = render_email_template(ROOM_UNCONFIRM_TITLE, ROOM_UNCONFIRM_TEXT['html'].format(member.name, room_order.room_name, issues_html))
plain_text = MIMEText(plain_body, "plain")
html_text = MIMEText(html_body, "html")
message = MIMEMultipart("alternative")
@@ -32,6 +34,11 @@ def send_unconfirm_message (room_order, room_errors, orders, title):
if len(memberMessages) == 0: return
+ with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT) as sender:
+ sender.login(SMTP_USER, SMTP_PASSWORD)
+ for message in memberMessages:
+ sender.sendmail(message['From'], message['to'], message.as_string())
+
def render_email_template(title = "", body = ""):
- tpl = app.ctx.tpl.get_template('email/comunication.html')
+ tpl = Environment(loader=FileSystemLoader("tpl"), autoescape=False).get_template('email/comunication.html')
return str(tpl.render(title=title, body=body))
\ No newline at end of file
diff --git a/ext.py b/ext.py
index 6c09b9c..376606a 100644
--- a/ext.py
+++ b/ext.py
@@ -1,12 +1,14 @@
from dataclasses import dataclass
-from sanic import Request, exceptions
+from sanic import Request, exceptions, Sanic
import httpx
import re
from utils import *
from config import *
from os.path import join
import json
+from sanic.log import logger
from time import time
+import asyncio
@dataclass
class Order:
@@ -169,11 +171,11 @@ class Order:
for key in range(len(self.answers)):
if self.answers[key].get('question_identifier', None) == name:
if new_answer != None:
- print('EXISTING ANSWER UPDATE', name, '=>', new_answer)
+ if DEV_MODE and EXTRA_PRINTS: logger.debug('[ANSWER EDIT] EXISTING ANSWER UPDATE %s => %s', name, new_answer)
self.answers[key]['answer'] = new_answer
found = True
else:
- print('DEL ANSWER', name, '=>', new_answer)
+ if DEV_MODE and EXTRA_PRINTS: logger.debug('[ANSWER EDIT] DEL ANSWER %s => %s', name, new_answer)
del self.answers[key]
break
@@ -186,7 +188,7 @@ class Order:
for r in res['results']:
if r['identifier'] != name: continue
- print('ANSWER UPDATE', name, '=>', new_answer)
+ if DEV_MODE and EXTRA_PRINTS: logger.debug(f'[ANSWER EDIT] %s => %s', name, new_answer)
self.answers.append({
'question': r['id'],
'answer': new_answer,
@@ -196,7 +198,7 @@ class Order:
async def send_answers(self):
async with httpx.AsyncClient() as client:
- print("POSITION ID IS", self.position_id)
+ if DEV_MODE and EXTRA_PRINTS: logger.debug("[ANSWER POST] POSITION ID IS %s", self.position_id)
for i, ans in enumerate(self.answers):
if TYPE_OF_QUESTIONS[ans['question']] == QUESTION_TYPES["multiple_choice_from_list"]: # if multiple choice
@@ -213,7 +215,7 @@ class Order:
if res.status_code != 200:
for ans, err in zip(self.answers, res.json()['answers']):
if err:
- print('ERROR ON', ans, err)
+ logger.error ('[ANSWERS SENDING] ERROR ON', ans, err)
raise exceptions.ServerError('There has been an error while updating this answers.')
@@ -251,18 +253,21 @@ async def get_order(request: Request=None):
class OrderManager:
def __init__(self):
self.lastCacheUpdate = 0
+ self.updating = False
self.empty()
def empty(self):
self.cache = {}
self.order_list = []
- async def updateCache(self):
+ # Will fill cache once the last cache update is greater than cache expire time
+ async def update_cache(self):
t = time()
- if(t - self.lastCacheUpdate > CACHE_EXPIRE_TIME):
- print("[TIME] Re-filling cache!")
+ to_return = False
+ if(t - self.lastCacheUpdate > CACHE_EXPIRE_TIME and not self.updating):
+ to_return = True
await self.fill_cache()
- self.lastCacheUpdate = t
+ return to_return
def add_cache(self, order):
self.cache[order.code] = order
@@ -275,30 +280,44 @@ class OrderManager:
self.order_list.remove(code)
async def fill_cache(self):
+ # Check cache lock
+ if self.updating == True: return
+ # Set cache lock
+ self.updating = True
+ start_time = time()
+ logger.info("[CACHE] Filling cache...")
+ # Index item's ids
await load_items()
+
+ # Index questions' types
await load_questions()
+
+ # Clear cache data completely
self.empty()
p = 0
-
- async with httpx.AsyncClient() as client:
- while 1:
- p += 1
- res = await client.get(join(base_url_event, f"orders/?page={p}"), headers=headers)
-
- if res.status_code == 404: break
-
- data = res.json()
- for o in data['results']:
- o = Order(o)
- if o.status in ['canceled', 'expired']:
- self.remove_cache(o.code)
- else:
- self.add_cache(Order(o))
- self.lastCacheUpdate = time()
- for o in self.cache.values():
- if o.code == o.room_id:
- print(o.room_name)
- await validate_room(None, o, self)
+ try:
+ async with httpx.AsyncClient() as client:
+ while 1:
+ p += 1
+ res = await client.get(join(base_url_event, f"orders/?page={p}"), headers=headers)
+ if res.status_code == 404: break
+ # Parse order data
+ data = res.json()
+ for o in data['results']:
+ o = Order(o)
+ if o.status in ['canceled', 'expired']:
+ self.remove_cache(o.code)
+ else:
+ self.add_cache(Order(o))
+ self.lastCacheUpdate = time()
+ logger.info(f"[CACHE] Cache filled in {self.lastCacheUpdate - start_time}s.")
+ except Exception as ex:
+ logger.error("[CACHE] Error while refreshing cache.", ex)
+ finally:
+ self.updating = False
+ # Validating rooms
+ rooms = list(filter(lambda o: (o.code == o.room_id), self.cache.values()))
+ asyncio.create_task(validate_rooms(None, rooms, self))
async def get_order(self, request=None, code=None, secret=None, nfc_id=None, cached=False):
@@ -308,7 +327,7 @@ class OrderManager:
if order.nfc_id == nfc_id:
return order
- await self.updateCache()
+ await self.update_cache()
# If a cached order is needed, just get it if available
if code and cached and code in self.cache and time()-self.cache[code].time < 3600:
return self.cache[code]
@@ -319,7 +338,7 @@ class OrderManager:
secret = request.cookies.get("foxo_secret")
if re.match('^[A-Z0-9]{5}$', code or '') and (secret is None or re.match('^[a-z0-9]{16,}$', secret)):
- print('Fetching', code, 'with secret', secret)
+ if DEV_MODE and EXTRA_PRINTS: logger.debug(f'Fetching {code} with secret {secret}')
async with httpx.AsyncClient() as client:
res = await client.get(join(base_url_event, f"orders/{code}/"), headers=headers)
diff --git a/image_util.py b/image_util.py
index 09d7f58..75a7a07 100644
--- a/image_util.py
+++ b/image_util.py
@@ -1,7 +1,7 @@
from config import *
from PIL import Image, ImageDraw, ImageFont
from sanic import Blueprint, exceptions
-import textwrap
+from sanic.log import logger
jobs = []
@@ -31,6 +31,7 @@ def draw_profile (source, member, position, font, size=(170, 170), border_width=
idraw.text(name_loc, str(member['name']), font=font, fill=name_color)
async def generate_room_preview(request, code, room_data):
+ if code in jobs: raise exceptions.SanicException("Please try again later!", status_code=409)
font_path = f'res/font/NotoSans-Bold.ttf'
main_fill = (187, 198, 206)
propic_size = (170, 170)
@@ -67,7 +68,7 @@ async def generate_room_preview(request, code, room_data):
draw_profile(source, member, (propic_gap + (propic_total_width * m), 63), font, propic_size, border_width)
source.save(f'res/rooms/{code}.jpg', 'JPEG', quality=60)
except Exception as err:
- if EXTRA_PRINTS: print(err)
+ if EXTRA_PRINTS: logger.exception(str(err))
finally:
# Remove fault job
if len(jobs) > 0: jobs.pop()
diff --git a/messages.py b/messages.py
index 7051486..7358600 100644
--- a/messages.py
+++ b/messages.py
@@ -8,6 +8,6 @@ ROOM_ERROR_TYPES = {
ROOM_UNCONFIRM_TITLE = "Your room got unconfirmed"
ROOM_UNCONFIRM_TEXT = {
- 'html': "Hello {0}
We had to unconfirm your room '{1}' due to the following problem/s:
{2}
Please contact your room's owner or contact our support for further informations at https://furizon.net/contact/.
Thank you",
- 'plain': "Hello {0}\nWe had to unconfirm your room '{1}' due to the following problem/s:\n{2}\nPlease contact your room's owner or contact our support for further informations at https://furizon.net/contact/.\nThank you"
+ 'html': "Hello {0}
We had to unconfirm your room '{1}' due to the following issues:
Please contact your room's owner or contact our support for further informations at https://furizon.net/contact/. ⚠️ Your room hasn't been confirmed yet. Unconfirmed rooms are subject to changes by the staff as we optimize for hotel capacity. ⚠️ There are some issues with your room:
-
Thank you.
Manage booking",
+ 'plain': "Hello {0}\nWe had to unconfirm your room '{1}' due to the following issues:\n{2}\nPlease contact your room's owner or contact our support for further informations at https://furizon.net/contact/.\nThank you\n\nTo manage your booking: https://reg.furizon.net/manage/welcome"
}
\ No newline at end of file
diff --git a/propic.py b/propic.py
index 91adee7..3c4138a 100644
--- a/propic.py
+++ b/propic.py
@@ -13,7 +13,7 @@ bp = Blueprint("propic", url_prefix="/manage/propic")
async def resetDefaultPropic(request, order: Order, isFursuiter, sendAnswer=True):
s = "_fursuiter" if isFursuiter else ""
if (EXTRA_PRINTS):
- print("Resetting {fn} picture for {orderCod}".format(fn="Badge" if not isFursuiter else "fursuit", orderCod = order.code))
+ logger.info("Resetting {fn} picture for {orderCod}".format(fn="Badge" if not isFursuiter else "fursuit", orderCod = order.code))
with open("res/propic/default.png", "rb") as f:
data = f.read()
f.close()
@@ -56,7 +56,7 @@ async def upload_propic(request, order: Order):
# Check max file size
if EXTRA_PRINTS:
- print(f"Image {fn} weight: {len(body[0].body)} bytes")
+ logger.debug (f"Image {fn} weight: {len(body[0].body)} bytes")
if len(body[0].body) > PROPIC_MAX_FILE_SIZE:
raise exceptions.BadRequest("File size too large for " + ("Profile picture" if fn == 'propic' else 'Fursuit picture'))
diff --git a/res/styles/admin.css b/res/styles/admin.css
index 665f287..d1b0f9b 100644
--- a/res/styles/admin.css
+++ b/res/styles/admin.css
@@ -1,4 +1,5 @@
div.room-actions {
+ container-name: room-actions;
float: right;
}
diff --git a/room.py b/room.py
index 226024e..4474c24 100644
--- a/room.py
+++ b/room.py
@@ -204,8 +204,8 @@ async def approve_roomreq(request, code, order: Order):
await order.edit_answer('pending_roommates', (','.join([x for x in order.pending_roommates if x != pending_member.code]) or None))
await pending_member.send_answers()
- await order.send_answers(order.code)
- remove_room_preview()
+ await order.send_answers()
+ remove_room_preview(order.code)
return redirect('/manage/welcome')
@bp.route("/leave")
@@ -363,14 +363,14 @@ def remove_room_preview(code):
try:
if os.path.exists(preview_file): os.remove(preview_file)
except Exception as ex:
- if (EXTRA_PRINTS): print(ex)
+ if (EXTRA_PRINTS): logger.exception(str(ex))
@bp.route("/view/")
async def get_view(request, code):
room_file_name = f"res/rooms/{code}.jpg"
room_data = await get_room(request, code)
-
- if not os.path.exists(room_file_name) and code not in jobs:
+ if not room_data: raise exceptions.NotFound("No room was found with that code.")
+ if not os.path.exists(room_file_name):
await generate_room_preview(request, code, room_data)
tpl = request.app.ctx.tpl.get_template('view_room.html')
return html(tpl.render(preview=room_file_name, room_data=room_data))
\ No newline at end of file
diff --git a/stats.py b/stats.py
index d9759c1..15ef5cf 100644
--- a/stats.py
+++ b/stats.py
@@ -6,7 +6,7 @@ bp = Blueprint("stats", url_prefix="/manage")
@bp.route("/sponsorcount")
async def sponsor_count(request, order: Order):
- await request.app.ctx.om.updateCache()
+ await request.app.ctx.om.update_cache()
orders = {key:value for key,value in sorted(request.app.ctx.om.cache.items(), key=lambda x: x[1].ans('fursona_name')) if value.status not in ['c', 'e']}
tpl = request.app.ctx.tpl.get_template('sponsorcount.html')
@@ -14,7 +14,7 @@ async def sponsor_count(request, order: Order):
@bp.route("/nosecount")
async def nose_count(request, order: Order):
- await request.app.ctx.om.updateCache()
+ await request.app.ctx.om.update_cache()
orders = {key:value for key,value in sorted(request.app.ctx.om.cache.items(), key=lambda x: len(x[1].room_members), reverse=True) if value.status not in ['c', 'e']}
tpl = request.app.ctx.tpl.get_template('nosecount.html')
@@ -22,7 +22,7 @@ async def nose_count(request, order: Order):
@bp.route("/fursuitcount")
async def fursuit_count(request, order: Order):
- await request.app.ctx.om.updateCache()
+ await request.app.ctx.om.update_cache()
orders = {key:value for key,value in sorted(request.app.ctx.om.cache.items(), key=lambda x: x[1].ans('fursona_name')) if value.status not in ['c', 'e']}
tpl = request.app.ctx.tpl.get_template('fursuitcount.html')
diff --git a/tpl/admin.html b/tpl/admin.html
index 50b9328..0b2829f 100644
--- a/tpl/admin.html
+++ b/tpl/admin.html
@@ -12,6 +12,7 @@
Admin panel
Clear cache
Manage rooms
+ Verify Rooms
diff --git a/tpl/blocks/room.html b/tpl/blocks/room.html
index 8d0b839..1b9ea67 100644
--- a/tpl/blocks/room.html
+++ b/tpl/blocks/room.html
@@ -14,16 +14,6 @@
{% if order.room_id and not order.room_confirmed %}
- {% for issue in issues %}
-
-
{{body}}
-{{body}}
+Welcome to the nosecount page! Here you can see all of the available rooms at the convention, as well as the occupants currently staying in each room. Use this page to find your friends and plan your meet-ups.
- {% for o in orders.values() %} - {% if o.code == o.room_id and o.room_confirmed %} + {% for o in orders.values() if (o.code == o.room_id and o.room_confirmed) %} + {% if loop.first %} +These unconfirmed rooms are still being organized and may be subject to change. These rooms may also have openings for additional roommates. If you are interested in sharing a room, you can use this page to find potential roommates
{% endif %} -These furs have not yet secured a room for the convention. If you see your name on this list, please make sure to secure a room before the deadline to avoid being placed in a random room. If you are looking for a roommate or have an open spot in your room, you can use this page to find and connect with other furries who are also looking for housing 🎲
These furs will not stay in our hotels, but may be there with us just a few days!