Compare commits

..

No commits in common. "f8ed348d51ad9a4fa3b854b9666b8254c3db2332" and "25ed127e81cb61e5847995b51bf0ab2f95549e34" have entirely different histories.

24 changed files with 85 additions and 557 deletions

View File

@ -1,72 +0,0 @@
from email.mime.text import MIMEText
from sanic import response, redirect, Blueprint, exceptions
from config import *
from utils import *
from ext import *
import sqlite3
import smtplib
import random
import string
import httpx
import json
bp = Blueprint("admin", url_prefix="/manage/admin")
def credentialsCheck (request, order:Order):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if not order.isAdmin() : raise exceptions.Forbidden("Birichino :)")
@bp.get('/cache/clear')
async def clearCache(request, order:Order):
credentialsCheck(request, order)
await request.app.ctx.om.fill_cache()
return redirect(f'/manage/admin')
@bp.get('/room/unconfirm/<code>')
async def unconfirmRoom(request, code, order:Order):
credentialsCheck(request, order)
dOrder = await getOrderByCode_safe(request, code)
if(not dOrder.room_confirmed):
raise exceptions.BadRequest("Room is not confirmed!")
ppl = getPeopleInRoomByRoomId(request, code)
for p in ppl:
await p.edit_answer('room_confirmed', "False")
await p.send_answers()
return redirect(f'/manage/nosecount')
@bp.get('/room/delete/<code>')
async def deleteRoom(request, code, order:Order):
credentialsCheck(request, order)
dOrder = await getOrderByCode_safe(request, code)
ppl = getPeopleInRoomByRoomId(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 renameRoom(request, code, order:Order):
credentialsCheck(request, order)
dOrder = await getOrderByCode_safe(request, code)
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')

22
app.py
View File

@ -31,9 +31,8 @@ from stats import bp as stats_bp
from api import bp as api_bp
from carpooling import bp as carpooling_bp
from checkin import bp as checkin_bp
from admin import bp as admin_bp
app.blueprint([room_bp, karaoke_bp, propic_bp, export_bp, stats_bp, api_bp, carpooling_bp, checkin_bp, admin_bp])
app.blueprint([room_bp, karaoke_bp, propic_bp, export_bp, stats_bp, api_bp, carpooling_bp, checkin_bp])
@app.exception(exceptions.SanicException)
async def clear_session(request, exception):
@ -41,8 +40,8 @@ async def clear_session(request, exception):
r = html(tpl.render(exception=exception))
if exception.status_code == 403:
r.delete_cookie("foxo_code")
r.delete_cookie("foxo_secret")
del r.cookies["foxo_code"]
del r.cookies["foxo_secret"]
return r
@app.before_server_start
@ -64,12 +63,8 @@ async def main_start(*_):
app.ctx.tpl = Environment(loader=FileSystemLoader("tpl"), autoescape=True)
app.ctx.tpl.globals.update(time=time)
app.ctx.tpl.globals.update(PROPIC_DEADLINE=PROPIC_DEADLINE)
app.ctx.tpl.globals.update(ITEMS_ID_MAP=ITEMS_ID_MAP)
app.ctx.tpl.globals.update(ITEM_VARIATIONS_MAP=ITEM_VARIATIONS_MAP)
app.ctx.tpl.globals.update(ITEM_IDS=ITEM_IDS)
app.ctx.tpl.globals.update(ROOM_TYPE_NAMES=ROOM_TYPE_NAMES)
app.ctx.tpl.globals.update(PROPIC_MIN_SIZE=PROPIC_MIN_SIZE)
app.ctx.tpl.globals.update(PROPIC_MAX_SIZE=PROPIC_MAX_SIZE)
app.ctx.tpl.globals.update(PROPIC_MAX_FILE_SIZE=sizeof_fmt(PROPIC_MAX_FILE_SIZE))
app.ctx.tpl.globals.update(int=int)
app.ctx.tpl.globals.update(len=len)
@ -162,15 +157,6 @@ async def download_ticket(request, order: Order):
return raw(res.content, content_type='application/pdf')
@app.route("/manage/admin")
async def admin(request, order: Order):
await request.app.ctx.om.updateCache()
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if not order.isAdmin(): raise exceptions.Forbidden("Birichino :)")
tpl = app.ctx.tpl.get_template('admin.html')
return html(tpl.render(order=order))
@app.route("/manage/logout")
async def logour(request):
raise exceptions.Forbidden("You have been logged out.")

View File

@ -8,98 +8,47 @@ base_url = "http://urlllllllllllllllllllll/api/v1/"
base_url_event = f"{base_url}organizers/{ORGANIZER}/events/{EVENT_NAME}/"
PROPIC_DEADLINE = 9999999999
PROPIC_MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB
PROPIC_MAX_SIZE = (2048, 2048) # (Width, Height)
PROPIC_MIN_SIZE = (125, 125) # (Width, Height)
FILL_CACHE = True
CACHE_EXPIRE_TIME = 60 * 60 * 4
DEV_MODE = True
# Metadata property for item-id mapping
METADATA_NAME = "item_name"
# Metadata property for internal category mapping (not related to pretix's category)
METADATA_CATEGORY = "category_name"
# Maps Products metadata name <--> ID
ITEMS_ID_MAP = {
'early_bird_ticket': 126,
'regular_ticket': 127,
'staff_ticket': 155,
'daily_ticket': 162,
'sponsorship_item': 129,
'early_arrival_admission': 133,
'late_departure_admission': 134,
'membership_card_item': 128,
ITEM_IDS = {
'ticket': [126, 127, 155],
'membership_card': [128,],
'sponsorship': [55, 56], # first one = normal, second = super
'early_arrival': [133],
'late_departure': [134],
'room': 135,
'bed_in_room': 153,
'room_type': 135,
'room_guest': 136,
'daily_1': 163,
'daily_2': 164,
'daily_3': 165,
'daily_4': 166,
'daily_5': None
'daily': 162,
'daily_addons': [163, 164, 165, 166] #This should be in date order. If there are holes in the daily-span, insert an unexisting id
}
# Maps Products' variants metadata name <--> ID
ITEM_VARIATIONS_MAP = {
'sponsorship_item': {
'sponsorship_item_normal': 55,
'sponsorship_item_super': 56
},
'bed_in_room': {
'bed_in_room_main_1': 83,
'bed_in_room_main_2': 67,
'bed_in_room_main_3': 68,
'bed_in_room_main_4': 69,
'bed_in_room_main_5': 70,
'bed_in_room_overflow1_2': 75,
},
'room_type': {
'single': 57,
'double': 58,
'triple': 59,
'quadruple': 60,
'quintuple': 61
},
'room_guest': {
'single': 57,
'double': 58,
'triple': 59,
'quadruple': 60,
'quintuple': 61
}
}
ADMINS_PRETIX_ROLE_NAMES = ["Reserved Area admin", "main staff"]
# Links Products' variants' ids with the internal category name
CATEGORIES_LIST_MAP = {
'tickets': [],
'memberships': [],
'sponsorships': [],
'tshirts': [],
'extra_days': [],
'rooms': [],
'dailys': []
}
# Create a bunch of "room" items which will get added to the order once somebody gets a room.
# Map item_name -> room capacity
ROOM_CAPACITY_MAP = {
# Map variationId -> numberOfPeopleInRoom
ROOM_MAP = {
# SACRO CUORE
'bed_in_room_main_1': 1,
'bed_in_room_main_2': 2,
'bed_in_room_main_3': 3,
'bed_in_room_main_4': 4,
'bed_in_room_main_5': 5,
83: 1,
67: 2,
68: 3,
69: 4,
70: 5,
# OVERFLOW 1
'bed_in_room_overflow1_2': 2,
75: 2
}
# Autofilled
ROOM_TYPE_NAMES = { }
ROOM_TYPE_NAMES = {
83: "Park Hotel Sacro Cuore (main hotel) - Single",
67: "Park Hotel Sacro Cuore (main hotel) - Double",
68: "Park Hotel Sacro Cuore (main hotel) - Triple",
69: "Park Hotel Sacro Cuore (main hotel) - Quadruple",
70: "Park Hotel Sacro Cuore (main hotel) - Quintuple",
# OVERFLOW 1
75: "Hotel San Valier (overflow hotel) - Double"
}
# This is used for feedback sending inside of the app. Feedbacks will be sent to the specified chat using the bot api id.
TG_BOT_API = '123456789:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'

View File

@ -1,23 +0,0 @@
# To connect with pretix's data
# WIP to not commit
# DrewTW
import string
import httpx
import json
from ext import *
from config import *
from sanic import response
from sanic import Blueprint
import logging
log = logging.getLogger()
def checkConfig():
if (not DEV_MODE) and DUMMY_DATA:
log.warn('It is strongly unadvised to use dummy data in production')
def getOrders(page):
return None
def getOrder(code):
return None

View File

@ -8,7 +8,7 @@ bp = Blueprint("export", url_prefix="/manage/export")
@bp.route("/export.csv")
async def export_csv(request, order: Order):
if not order: raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if not order.isAdmin(): raise exceptions.Forbidden("Birichino :)")
if order.code not in ADMINS: raise exceptions.Forbidden("Birichino :)")
page = 0
orders = {}

42
ext.py
View File

@ -45,7 +45,7 @@ class Order:
self.country = idata['country']
for p in self.data['positions']:
if p['item'] in CATEGORIES_LIST_MAP['tickets']:
if p['item'] in (ITEM_IDS['ticket'] + [ITEM_IDS['daily']]):
self.position_id = p['id']
self.position_positionid = p['positionid']
self.position_positiontypeid = p['item']
@ -55,33 +55,32 @@ class Order:
self.answers[i]['answer'] = "file:keep"
self.barcode = p['secret']
self.checked_in = bool(p['checkins'])
if p['item'] in CATEGORIES_LIST_MAP['dailys']:
if p['item'] == ITEM_IDS['daily']:
self.daily = True
self.dailyDays.append(CATEGORIES_LIST_MAP['dailys'].index(p['item']))
if p['item'] in CATEGORIES_LIST_MAP['memberships']:
if p['item'] in ITEM_IDS['daily_addons']:
self.daily = True
self.dailyDays.append(ITEM_IDS['daily_addons'].index(p['item']))
if p['item'] in ITEM_IDS['membership_card']:
self.has_card = True
if p['item'] == ITEMS_ID_MAP['sponsorship_item']:
sponsorshipType = keyFromValue(ITEM_VARIATIONS_MAP['sponsorship_item'], p['variation'])
self.sponsorship = sponsorshipType[0].replace ('sponsorship_item_', '') if len(sponsorshipType) > 0 else None
if p['item'] in ITEM_IDS['sponsorship']:
self.sponsorship = 'normal' if p['variation'] == ITEMS_IDS['sponsorship'][0] else 'super'
if p['attendee_name']:
self.first_name = p['attendee_name_parts']['given_name']
self.last_name = p['attendee_name_parts']['family_name']
if p['item'] == ITEMS_ID_MAP['early_arrival_admission']:
if p['item'] == ITEM_IDS['early_arrival']:
self.has_early = True
if p['item'] == ITEMS_ID_MAP['late_departure_admission']:
if p['item'] == ITEM_IDS['late_departure']:
self.has_late = True
if p['item'] == ITEMS_ID_MAP['bed_in_room']:
roomTypeLst = keyFromValue(ITEM_VARIATIONS_MAP['bed_in_room'], p['variation'])
roomTypeId = roomTypeLst[0] if len(roomTypeLst) > 0 else None
if p['item'] == ITEM_IDS['bed_in_room']:
self.bed_in_room = p['variation']
self.room_person_no = ROOM_CAPACITY_MAP[roomTypeId] if roomTypeId in ROOM_CAPACITY_MAP else None
self.room_person_no = ROOM_MAP[self.bed_in_room] if self.bed_in_room in ROOM_MAP else None
self.total = float(data['total'])
self.fees = 0
@ -97,8 +96,6 @@ class Order:
self.payment_provider = data['payment_provider']
self.comment = data['comment']
self.phone = data['phone']
self.loadAns()
def loadAns(self):
self.shirt_size = self.ans('shirt_size')
self.is_artist = True if self.ans('is_artist') != 'No' else False
self.is_fursuiter = True if self.ans('is_fursuiter') != 'No' else False
@ -125,8 +122,8 @@ class Order:
self.nfc_id = self.ans('nfc_id')
self.can_scan_nfc = True if self.ans('can_scan_nfc') != 'No' else False
self.actual_room = self.ans('actual_room')
self.staff_role = self.ans('staff_role')
self.telegram_username = self.ans('telegram_username').strip('@') if self.ans('telegram_username') else None
def __getitem__(self, var):
return self.data[var]
@ -139,12 +136,6 @@ class Order:
return a['answer']
return None
def isBadgeValid (self):
return self.ans('propic') and (not self.is_fursuiter or self.ans('propic_fursuiter'))
def isAdmin (self):
return self.code in ADMINS or self.staff_role in ADMINS_PRETIX_ROLE_NAMES
async def edit_answer_fileUpload(self, name, fileName, mimeType, data : bytes):
if(mimeType != None and data != None):
async with httpx.AsyncClient() as client:
@ -156,7 +147,6 @@ class Order:
await self.edit_answer(name, res['id'])
else:
await self.edit_answer(name, None)
self.loadAns()
async def edit_answer(self, name, new_answer):
found = False
@ -178,6 +168,7 @@ class Order:
async with httpx.AsyncClient() as client:
res = await client.get(join(base_url_event, 'questions/'), headers=headers)
res = res.json()
for r in res['results']:
if r['identifier'] != name: continue
@ -187,7 +178,6 @@ class Order:
'answer': new_answer,
'options': r['options']
})
self.loadAns()
async def send_answers(self):
async with httpx.AsyncClient() as client:
@ -214,7 +204,6 @@ class Order:
self.pending_update = False
self.time = -1
self.loadAns()
@dataclass
class Quotas:
@ -265,7 +254,6 @@ class OrderManager:
self.order_list.remove(code)
async def fill_cache(self):
await loadItems()
await loadQuestions()
self.empty()
p = 0

View File

@ -10,7 +10,7 @@ bp = Blueprint("karaoke", url_prefix="/manage/karaoke")
@bp.get("/admin")
async def show_songs(request, order: Order):
if not order.isAdmin():
if order.code not in ADMINS:
raise exceptions.Forbidden("Birichino")
orders = [x for x in request.app.ctx.om.cache.values() if x.karaoke_songs]
@ -28,7 +28,7 @@ async def show_songs(request, order: Order):
@bp.post("/approve")
async def approve_songs(request, order: Order):
if not order.isAdmin():
if order.code not in ADMINS:
raise exceptions.Forbidden("Birichino")
for song in request.form:
@ -44,7 +44,7 @@ async def sing_song(request, order: Order, songname):
if not order: raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if not order.isAdmin():
if order.code not in ADMINS:
raise exceptions.Forbidden("Birichino")
songname = unquote(songname)

View File

@ -6,19 +6,14 @@ from PIL import Image
from io import BytesIO
from hashlib import sha224
from time import time
import os
bp = Blueprint("propic", url_prefix="/manage/propic")
async def resetDefaultPropic(request, order: Order, isFursuiter, sendAnswer=True):
s = "_fursuiter" if isFursuiter else ""
if (DEV_MODE):
print("Resetting {fn} picture for {orderCod}".format(fn="Badge" if not isFursuiter else "fursuit", orderCod = order.code))
print("Resetting default propic")
with open("res/propic/default.png", "rb") as f:
data = f.read()
f.close()
os.remove(f"res/propic/{order.ans(f'propic{s}')}") # converted file
os.remove(f"res/propic/{order.ans(f'propic{s}').split(".jpg")[0]}_original.jpg") # original file
await order.edit_answer_fileUpload(f'propic{s}_file', f'propic{s}_file_{order.code}_default.png', 'image/png', data)
if(sendAnswer):
await order.send_answers()
@ -46,30 +41,17 @@ async def upload_propic(request, order: Order):
if not body[0].body: continue
# Check max file size
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'))
h = sha224(body[0].body).hexdigest()[:32]
errorDetails = ''
try:
img = Image.open(BytesIO(body[0].body))
width, height = img.size
# Checking for min / max size
if width < PROPIC_MIN_SIZE[0] or height < PROPIC_MIN_SIZE[1]:
errorDetails = "Image too small [{width}x{width}] for {pfpn}".format(width=width, pfpn=("Profile picture" if fn == 'propic' else 'Fursuit picture'))
raise exceptions.BadRequest(errorDetails)
if(img.size[0] > 2048 or img.size[1] > 2048):
raise exceptions.BadRequest("Maximum allowed dimensions: 2048x2048")
if width > PROPIC_MAX_SIZE[0] or height > PROPIC_MAX_SIZE[1]:
errorDetails = "Image too big [{width}x{width}] for {pfpn}".format(width=width, pfpn=("Profile picture" if fn == 'propic' else 'Fursuit picture'))
raise exceptions.BadRequest(errorDetails)
with open(f"res/propic/{fn}_{order.code}_original.jpg", "wb") as f:
with open(f"res/propic/{fn}_{order.code}_original", "wb") as f:
f.write(body[0].body)
f.flush()
f.close()
width, height = img.size
aspect_ratio = width/height
if aspect_ratio > 1:
crop_amount = (width - height) / 2
@ -79,22 +61,19 @@ async def upload_propic(request, order: Order):
img = img.crop((0, crop_amount, width, height - crop_amount))
img = img.convert('RGB')
width, height = img.size
img.thumbnail((512,512))
imgBytes = BytesIO()
img.save(imgBytes, format='jpeg')
imgBytes = imgBytes.getvalue()
with open(f"res/propic/{fn}_{order.code}_{h}.jpg", "wb") as f:
f.write(imgBytes)
f.flush()
f.close()
await order.edit_answer_fileUpload(f'{fn}_file', f'{fn}_file_{order.code}_{h}.jpg', 'image/jpeg', imgBytes)
except Exception:
import traceback
if DEV_MODE: print(traceback.format_exc())
raise exceptions.BadRequest(errorDetails if errorDetails else "The image you uploaded is not valid.")
print(traceback.format_exc())
raise exceptions.BadRequest("The image you uploaded is not valid.")
else:
await order.edit_answer(fn, f"{fn}_{order.code}_{h}.jpg")

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg>

Before

Width:  |  Height:  |  Size: 300 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M440-440q17 0 28.5-11.5T480-480q0-17-11.5-28.5T440-520q-17 0-28.5 11.5T400-480q0 17 11.5 28.5T440-440ZM280-120v-80l240-40v-445q0-15-9-27t-23-14l-208-34v-80l220 36q44 8 72 41t28 77v512l-320 54Zm-160 0v-80h80v-560q0-34 23.5-57t56.5-23h400q34 0 57 23t23 57v560h80v80H120Zm160-80h400v-560H280v560Z"/></svg>

Before

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-200h57l391-391-57-57-391 391v57Zm-80 80v-170l528-527q12-11 26.5-17t30.5-6q16 0 31 6t26 18l55 56q12 11 17.5 26t5.5 30q0 16-5.5 30.5T817-647L290-120H120Zm640-584-56-56 56 56Zm-141 85-28-29 57 57-29-28Z"/></svg>

Before

Width:  |  Height:  |  Size: 310 B

View File

@ -1,28 +0,0 @@
function confirmAction (intent, sender) {
if (['rename', 'unconfirm', 'delete'].includes (intent) == false) return
let href = sender.getAttribute('action')
let intentTitle = document.querySelector("#intentText")
let intentEdit = document.querySelector("#intentRename")
let intentEditPanel = document.querySelector("#intentEditPanel")
let intentFormAction = document.querySelector("#intentFormAction")
let intentSend = document.querySelector("#intentSend")
// Resetting ui
intentEdit.setAttribute('required', false)
intentFormAction.setAttribute('method', 'GET')
intentEditPanel.style.display = 'none';
intentTitle.innerText = intent + ' room'
intentFormAction.setAttribute('action', href)
switch (intent){
case 'rename':
intentEditPanel.style.display = 'block';
intentEdit.setAttribute('required', true)
intentFormAction.setAttribute('method', 'POST')
break
case 'unconfirm':
break
case 'delete':
break
}
document.getElementById('modalRoomconfirm').setAttribute('open', 'true');
}

View File

@ -1,18 +0,0 @@
div.room-actions {
float: right;
}
div.room-actions > a {
background-color: var(--card-background-color);
font-size: 12pt;
padding: 0.7rem;
border-radius: 1rem;
}
div.room-actions > a:hover {
background-color: var(--primary-focus);
}
div.room-actions > a.act-del:hover {
background-color: var(--del-color);
}

View File

@ -1,7 +1,5 @@
/* Other blocks' styles */
@import url('propic.css');
@import url('admin.css');
@import url('room.css');
summary:has(span.status) {
background-color: #ffaf0377;

View File

@ -1,14 +0,0 @@
span.nsc-room-counter {
font-size: medium;
color: var(--color);
}
#intentFormAction #intentText {
text-transform: capitalize;
}
@media only screen and (max-width: 500px) {
div.room-actions a>span {
display: none;
}
}

34
room.py
View File

@ -49,7 +49,7 @@ async def delete_room(request, order: Order):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if order.room_owner:
if order.room_id != order.code:
raise exceptions.BadRequest("You are not allowed to delete room of others.")
if order.ans('room_confirmed'):
@ -177,9 +177,6 @@ async def approve_roomreq(request, code, order: Order):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if not order.room_owner:
raise exceptions.BadRequest("You are not the owner of the room!")
if not code in order.pending_roommates:
raise exceptions.BadRequest("You cannot accept people that didn't request to join your room")
@ -234,9 +231,6 @@ async def reject_roomreq(request, code, order: Order):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if not order.room_owner:
raise exceptions.BadRequest("You are not the owner of the room!")
if not code in order.pending_roommates:
raise exceptions.BadRequest("You cannot reject people that didn't request to join your room")
@ -259,32 +253,6 @@ async def reject_roomreq(request, code, order: Order):
return redirect('/manage/welcome')
@bp.post("/rename")
async def rename_room(request, order: Order):
if not order:
raise exceptions.Forbidden("You have been logged out. Please access the link in your E-Mail to login again!")
if not order.room_owner:
raise exceptions.BadRequest("You are not the owner of the room!")
if not order.room_id:
raise exceptions.BadRequest("Try joining a room before renaming it.")
if order.room_confirmed:
raise exceptions.BadRequest("You can't rename a confirmed room!")
if order.room_id != order.code:
raise exceptions.BadRequest("You are not allowed to rename rooms of others.")
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 order.edit_answer("room_name", name)
await order.send_answers()
return redirect('/manage/welcome')
@bp.route("/confirm")
async def confirm_room(request, order: Order, quotas: Quotas):
if not order:

View File

@ -1,18 +0,0 @@
{% extends "base.html" %}
{% block title %}Admin panel{% endblock %}
{% block main %}
<main class="container">
<header>
<picture>
<source srcset="/res/furizon.png" media="(prefers-color-scheme:dark)">
<img src="/res/furizon-light.png" style="height:4rem;text-align:center;">
</picture>
</header>
<!-- Quick controls -->
<h2>Admin panel</h2>
<a href="/manage/admin/cache/clear" role="button" title="Reload the orders' data and the re-sync items' indexes from pretix">Clear cache</a>
<a href="/manage/nosecount" role="button" title="Reload the orders' data and the re-sync items' indexes from pretix">Manage rooms</a>
<hr>
</main>
{% endblock %}

View File

@ -7,7 +7,6 @@
<meta name="supported-color-schemes" content="light dark">
<link rel="stylesheet" href="/res/pico.min.css">
<link rel="stylesheet" href="/res/styles/base.css">
<link rel="icon" type="image/x-icon" href="/res/icons/favicon.ico">
<style>
.people div {text-align:center;}
.people h3, .people p, .people h5 {margin:0;}
@ -100,7 +99,6 @@
{% if order %}
<a href="/manage/carpooling">Carpooling</a>
<a style="float:right;" href="/manage/logout">Logout</a>
{% if order.isAdmin() %}<a style="float:right;" href="/manage/admin">Admin panel</a>{% endif %}
<a style="float:right;color: #b1b1b1;">Logged in as <i>{{order.ans('fursona_name')}}</i></a>
{% endif %}
<br clear="both" />

View File

@ -1,5 +1,5 @@
<details id="badge">
<summary role="button"><img src="/res/icons/badge.svg" class="icon"/>Badge Customization {% if not order.isBadgeValid() %}<span class="status">⚠️</span>{% endif %}</summary>
<summary role="button"><img src="/res/icons/badge.svg" class="icon"/>Badge Customization {% if not order.ans('propic') %}<span class="status">⚠️</span>{% endif %}</summary>
{# Badge is always shown #}
<h2>Badge</h2>
{% if order.propic_locked %}
@ -11,23 +11,23 @@
<form method="POST" enctype="multipart/form-data" action="/manage/propic/upload">
<div class="grid" style="text-align:center;margin-bottom:1em;">
<div>
{% with order=order, imgSrc='/res/propic/' + (order.ans('propic') or 'default.png'), effects = false %}
{% include 'blocks/propic.html' %}
{% endwith %}
<p>Normal Badge</p>
{% if not order.ans('propic') %}
<input type="file" value="" accept="image/jpeg,image/png" name="propic" />
{% endif %}
{% with order=order, imgSrc='/res/propic/' + (order.ans('propic') or 'default.png'), effects = true %}
{% include 'blocks/propic.html' %}
{% endwith %}
<p>Normal Badge</p>
</div>
{% if order.is_fursuiter %}
<div>
{% with order=order, imgSrc='/res/propic/' + (order.ans('propic_fursuiter') or 'default.png'), effects = false %}
{% include 'blocks/propic.html' %}
{% endwith %}
<p>Fursuit Badge</p>
{% if not order.ans('propic_fursuiter') %}
<input type="file" value="" accept="image/jpeg,image/png" name="propic_fursuiter" />
{% endif %}
{% with order=order, imgSrc='/res/propic/' + (order.ans('propic_fursuiter') or 'default.png'), effects = true %}
{% include 'blocks/propic.html' %}
{% endwith %}
<p>Fursuit Badge</p>
</div>
{% endif %}
</div>
@ -36,8 +36,7 @@
<p class="notice">⚠️ The deadline to upload pictures for the badge has expired. For last-minute changes, please contact the support over at <a href="mailto:info@furizon.net">info@furizon.net</a>. If your badge has been printed already, changing it will incur in a 2€ fee. You can also get extra badges at the reception for the same price. If you upload a propic now, it might not be printed on time.</p>
{% else %}
<p><em>
Min size: {{PROPIC_MIN_SIZE[0]}}x{{PROPIC_MIN_SIZE[1]}} - Max Size: {{PROPIC_MAX_FILE_SIZE}}, {{PROPIC_MAX_SIZE[0]}}x{{PROPIC_MAX_SIZE[1]}} - Formats: jpg, png<br />
Photos whose aspect ratio is not a square will be cropped
Min size: 64x64 - Max Size: 5MB, 2048x2048 - Formats: jpg, png<br />
Badge photos must clearly show the fursona/fursuit head.<br />Memes and low quality images will be removed and may limit your ability to upload pics in the future.
</em></p>
{% endif %}

View File

@ -1,5 +1,5 @@
<details id="room">
<summary role="button"><img src="/res/icons/bedroom.svg" class="icon"/> Accomodation & Roommates {% if not order.room_confirmed %}<span class="status">⚠️</span>{% endif %}</summary>
<summary role="button"><img src="/res/icons/bedroom.svg" class="icon"/> Accomodation & Roommates <span class="status">{% if not order.room_confirmed %}⚠️{% endif %}</span></summary>
<h2 style="margin-bottom:0;">Your room {% if room_members %}- {{room_members[0].ans('room_name')}}{% endif %}</h2>
<p><b>Room's type:</b> {{ROOM_TYPE_NAMES[order.bed_in_room]}}.</p>
<p class="notice" style="background:#0881c0"><b>Note! </b> Only people with the same room type can be roommates. If you need help, contact the <a href="https://furizon.net/contact/">Furizon's Staff</a>.</p>
@ -77,17 +77,13 @@
<p class="grid">
{% if order.room_owner %}
{% if not order.room_confirmed %}
{% if len(room_members) == 1 %}
{% if len(room_members) == 1 and not order.room_confirmed %}
<a href="/manage/room/delete" role="button">Delete room</a>
{% endif %}
{% if not order.room_confirmed %}
{# <a role="button" {% if not room.forbidden and quota.get_left(len(room_members)) > 0 %}href="javascript:document.getElementById('modal-roomconfirm').setAttribute('open', 'true');"{% endif %}>Confirm <strong>{{[None,'single','double','triple','quadruple','quintuple'][len(room_members)]}}</strong> room</a> #}
<a role="button" {% if not room.forbidden and len(room_members) == order.room_person_no %}href="javascript:document.getElementById('modal-roomconfirm').setAttribute('open', 'true');"{% endif %}>Confirm <strong>{{[None,'single','double','triple','quadruple','quintuple'][order.room_person_no]}}</strong> room</a>
<a role="button" href="javascript:document.getElementById('modal-roomrename').setAttribute('open', 'true');">Rename room {{[None,'single','double','triple','quadruple','quintuple'][order.room_person_no]}} room</a>
{% endif %}
{% else %}
{% if order.room_id and not order.room_confirmed %}
@ -108,10 +104,8 @@
{% if person.status == 'pending' %}
<td><strong style="color:red;">UNPAID</strong></td>
{% endif %}
{% if order.room_owner %}
<td style="width:1%;white-space: nowrap;"><a role="button" href="/manage/room/approve/{{person.code}}">Approve</a></td>
<td style="width:1%;white-space: nowrap;"><a role="button" href="/manage/room/reject/{{person.code}}">Reject</a></td>
{% endif %}
</tr>
</div>

View File

@ -45,20 +45,7 @@
</article>
</dialog>
<dialog id="modal-roomrename">
<article>
<a href="#close" aria-label="Close" class="close" onClick="javascript:this.parentElement.parentElement.removeAttribute('open')"></a>
<h3>Rename this room</h3>
<p>Enter your room's new name!</p>
<p>This name will be public and shown in the room list, so nothing offensive! :)</p>
<form method="POST" action="/manage/room/rename">
<label for="name"></label>
<input type="text" name="name" required minlength="4" maxlength="64" />
<input type="submit" value="Rename room" />
</form>
</article>
</dialog>
{% endif %}

View File

@ -2,9 +2,6 @@
{% block title %}Furizon 2024 Nosecount{% endblock %}
{% block main %}
<main class="container">
{% if order.isAdmin() %}
<script src="/res/scripts/roomManager.js"></script>
{% endif %}
<header>
<picture>
<source srcset="/res/furizon.png" media="(prefers-color-scheme:dark)">
@ -14,22 +11,13 @@
<p>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.</p>
{% for o in orders.values() %}
{% if o.code == o.room_id and o.room_confirmed %}
<h4 style="margin-top:1em;">
<span>{{o.room_name}}</span>
{% if order.isAdmin() %}
<div class="room-actions">
<a onclick="confirmAction('rename', this)" action="/manage/admin/room/rename/{{o.code}}"><img src="/res/icons/pencil.svg" class="icon" /><span>Rename</span></a>
<a onclick="confirmAction('unconfirm', this)" action="/manage/admin/room/unconfirm/{{o.code}}"><img src="/res/icons/door_open.svg" class="icon" /><span>Unconfirm</span></a>
<a class="act-del" onclick="confirmAction('delete', this)" action="/manage/admin/room/delete/{{o.code}}"><img src="/res/icons/delete.svg" class="icon" /><span>Delete</span></a>
</div>
{% endif %}
</h4>
<h4 style="margin-top:1em;">{{o.room_name}}</h4>
<div class="grid people" style="padding-bottom:1em;">
{% for m in o.room_members %}
{% if m in orders %}
{% with person = orders[m] %}
<div style="margin-bottom: 1em;">
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = true, flag = true %}
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = false, flag = true %}
{% include 'blocks/propic.html' %}
{% endwith %}
<h5>{{person.ans('fursona_name')}}</h5>
@ -46,22 +34,14 @@
<h1>Unconfirmed rooms</h1>
<p>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</p>
{% endif %}
<h3>
<span>{{o.room_name}}</span>
{% if o.room_person_no - len(o.room_members) > 0 %} <span class="nsc-room-counter"> - Remaining slots: {{o.room_person_no - len(o.room_members)}}</span> {% endif %}
{% if order.isAdmin() %}
<div class="room-actions">
<a onclick="confirmAction('rename', this)" action="/manage/admin/room/rename/{{o.code}}"><img src="/res/icons/pencil.svg" class="icon" /><span>Rename</span></a>
<a class="act-del" onclick="confirmAction('delete', this)" action="/manage/admin/room/delete/{{o.code}}"><img src="/res/icons/delete.svg" class="icon" /><span>Delete</span></a>
</div>
{% endif %}
</h3>
<h3 style="display:inline-block">{{o.room_name}}</h3>
{% if o.room_person_no - len(o.room_members) > 0 %} <p style="display:inline-block"> - Remaining slots: {{o.room_person_no - len(o.room_members)}}</p> {% endif %}
<div class="grid people" style="padding-bottom:1em;">
{% for m in o.room_members %}
{% if m in orders %}
{% with person = orders[m] %}
<div style="margin-bottom: 1em;">
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = true, flag = true %}
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = false, flag = true %}
{% include 'blocks/propic.html' %}
{% endwith %}
<h5>{{person.ans('fursona_name')}}</h5>
@ -81,7 +61,7 @@
<div class="grid people" style="padding-bottom:1em;">
{% endif %}
<div style="margin-bottom: 1em;">
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = true, flag = true %}
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = false, flag = true %}
{% include 'blocks/propic.html' %}
{% endwith %}
@ -98,7 +78,7 @@
<div class="grid people" style="padding-bottom:1em;">
{% endif %}
<div style="margin-bottom: 1em;">
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = true, flag = true %}
{% with order=person, imgSrc='/res/propic/' + (person.ans('propic') or 'default.png'), effects = false, flag = true %}
{% include 'blocks/propic.html' %}
{% endwith %}
<h5>{{person.ans('fursona_name')}}</h5>
@ -106,22 +86,5 @@
{% if loop.last %}</div>{% endif %}
{% endfor %}
<form id="intentFormAction" method="GET" action="">
<dialog id="modalRoomconfirm">
<article>
<a href="#close" aria-label="Close" class="close" onClick="javascript:this.parentElement.parentElement.removeAttribute('open')"></a>
<h3 id="intentText">Confirm room edit</h3>
<p id="intentDescription"></p>
<div id="intentEditPanel">
<label for="name">Enter a new room name</label>
<input id="intentRename" name="name" type="text" value="" minlength="4" maxlength="64"/>
</div>
<footer>
<input id="intentSend" type="submit" value="Confirm" />
</footer>
</article>
</dialog>
</form>
</main>
{% endblock %}

105
utils.py
View File

@ -1,11 +1,7 @@
from os.path import join
from sanic import exceptions
from config import *
import httpx
METADATA_TAG = "meta_data"
VARIATIONS_TAG = "variations"
QUESTION_TYPES = { #https://docs.pretix.eu/en/latest/api/resources/questions.html
"number": "N",
"one_line_string": "S",
@ -37,104 +33,3 @@ async def loadQuestions():
data = res.json()
for q in data['results']:
TYPE_OF_QUESTIONS[q['id']] = q['type']
async def loadItems():
global ITEMS_ID_MAP
global ITEM_VARIATIONS_MAP
global CATEGORIES_LIST_MAP
global ROOM_TYPE_NAMES
async with httpx.AsyncClient() as client:
p = 0
while 1:
p += 1
res = await client.get(join(base_url_event, f"items/?page={p}"), headers=headers)
if res.status_code == 404: break
data = res.json()
for q in data['results']:
# Map item id
itemName = checkAndGetName ('item', q)
if itemName and itemName in ITEMS_ID_MAP:
ITEMS_ID_MAP[itemName] = q['id']
# If item has variations, map them, too
if itemName in ITEM_VARIATIONS_MAP and VARIATIONS_TAG in q:
isBedInRoom = itemName == 'bed_in_room'
for v in q[VARIATIONS_TAG]:
variationName = checkAndGetName('variation', v)
if variationName and variationName in ITEM_VARIATIONS_MAP[itemName]:
ITEM_VARIATIONS_MAP[itemName][variationName] = v['id']
if isBedInRoom and variationName in ITEM_VARIATIONS_MAP['bed_in_room']:
roomName = v['name'] if 'name' in v and isinstance(v['name'], str) else None
if not roomName and 'value' in v:
roomName = v['value'][list(v['value'].keys())[0]]
ROOM_TYPE_NAMES[v['id']] = roomName
# Adds itself to the category list
categoryName = checkAndGetCategory ('item', q)
if not categoryName: continue
CATEGORIES_LIST_MAP[categoryName].append(q['id'])
if (DEV_MODE):
print (f'Mapped Items:')
print (ITEMS_ID_MAP)
print (f'Mapped Variations:')
print (ITEM_VARIATIONS_MAP)
print (f'Mapped categories:')
print (CATEGORIES_LIST_MAP)
print (f'Mapped Rooms:')
print (ROOM_TYPE_NAMES)
# Tries to get an item name from metadata. Prints a warning if an item has no metadata
def checkAndGetName(type, q):
itemName = extractMetadataName(q)
if not itemName and DEV_MODE:
print (type + ' ' + q['id'] + ' has not been mapped.')
return itemName
def checkAndGetCategory (type, q):
categoryName = extractCategory (q)
if not categoryName and DEV_MODE:
print (type + ' ' + q['id'] + ' has no category set.')
return categoryName
# Checks if the item has specified metadata name
def internalNameCheck (toExtract, name):
return toExtract and name and METADATA_TAG in toExtract and toExtract[METADATA_TAG][METADATA_NAME] == str(name)
# Returns the item_name metadata from the item or None if not defined
def extractMetadataName (toExtract):
return extractData(toExtract, [METADATA_TAG, METADATA_NAME])
# Returns the category_name metadata from the item or None if not defined
def extractCategory (toExtract):
return extractData(toExtract, [METADATA_TAG, METADATA_CATEGORY])
def extractData (dataFrom, tags):
data = dataFrom
for t in tags:
if t not in data: return None
data = data[t]
return data
def keyFromValue(dict, value):
return [k for k,v in dict.items() if v == value]
def sizeof_fmt(num, suffix="B"):
for unit in ("", "K", "M", "G", "T", "P", "E", "Z"):
if abs(num) < 1000.0:
return f"{num:3.1f}{unit}{suffix}"
num /= 1000.0
return f"{num:.1f}Yi{suffix}"
async def getOrderByCode_safe(request, code):
res = await request.app.ctx.om.get_order(code=code)
if res is None:
raise exceptions.BadRequest(f"[getOrderByCode_safe] Code {code} not found!")
return res
def getPeopleInRoomByRoomId(request, roomId):
c = request.app.ctx.om.cache
ret = []
for person in c.values():
if person.room_id == roomId:
ret.append(person)
return ret