stranck-dev in drew-dev #17

Merged
drew merged 8 commits from stranck-dev into drew-dev 2024-02-13 15:47:38 +00:00
14 changed files with 232 additions and 73 deletions

4
.gitignore vendored
View File

@ -165,3 +165,7 @@ res/rooms/*
config.py
furizon_webinit_riverside2023.tar.gz
diomerdas
furizon.net/site/*
furizon.net.zip
stuff/secrets.py
backups/*

29
app.py
View File

@ -1,7 +1,7 @@
from sanic import Sanic, response, exceptions
from sanic.response import text, html, redirect, raw
from jinja2 import Environment, FileSystemLoader
from time import time
from time import time, sleep
import httpx
from os.path import join
from ext import *
@ -12,7 +12,9 @@ from io import BytesIO
from asyncio import Queue
from messages import LOCALES
import sqlite3
from sanic.log import logger
import requests
import sys
from sanic.log import logger, logging
app = Sanic(__name__)
app.static("/res", "res/")
@ -45,6 +47,7 @@ async def clear_session(request, exception):
@app.before_server_start
async def main_start(*_):
logger.info(f"[{app.name}] >>>>>> main_start <<<<<<")
logger.setLevel(LOG_LEVEL)
app.config.REQUEST_MAX_SIZE = PROPIC_MAX_FILE_SIZE * 3
@ -184,4 +187,24 @@ async def logout(request):
raise exceptions.Forbidden("You have been logged out.")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8188, dev=DEV_MODE, access_log=ACCESS_LOG)
# Wait for pretix in server reboot
# Using a docker configuration, pretix may be unable to talk with postgres if postgres' service started before it.
# To fix this issue I added a After=pretix.service to the [Unit] section of /lib/systemd/system/postgresql@.service
# to let it start in the correct order. The following piece of code makes sure that pretix is running and can talk to
# postgres before actually starting the reserved area, since this operation requires a cache-fill in startup
print("Waiting for pretix to be up and running", file=sys.stderr)
while True:
print("Trying connecting to pretix...", file=sys.stderr)
try:
res = requests.get(base_url_event, headers=headers)
res = res.json()
if(res['slug'] == EVENT_NAME):
print("Healtchecking...", file=sys.stderr)
res = requests.get(join(domain, "healthcheck"), headers=headers)
if(res.status_code == 200):
break
except:
pass
sleep(5)
print("Connected to pretix!", file=sys.stderr)
app.run(host="127.0.0.1", port=8188, dev=DEV_MODE, access_log=ACCESS_LOG)

View File

@ -1,10 +1,14 @@
from sanic.log import logging
LOG_LEVEL = logging.DEBUG
API_TOKEN = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
ORGANIZER = 'furizon'
EVENT_NAME = 'overlord'
HOSTNAME = 'reg.furizon.net'
headers = {'Host': HOSTNAME, 'Authorization': f'Token {API_TOKEN}'}
base_url = "http://urlllllllllllllllllllll/api/v1/"
domain = "http://urlllllllllllllllllllll/"
base_url = "{domain}api/v1/"
base_url_event = f"{base_url}organizers/{ORGANIZER}/events/{EVENT_NAME}/"
PROPIC_DEADLINE = 9999999999
@ -25,6 +29,9 @@ SMTP_HOST = 'host'
SMTP_PORT = 0
SMTP_USER = 'user'
SMTP_PASSWORD = 'pw'
EMAIL_SENDER_NAME = "Fantastic Furcon Wow"
EMAIL_SENDER_MAIL = "no-reply@thisIsAFantasticFurconWowItIsWonderful.cuteFurries.ovh"
SMPT_CLIENT_CLOSE_TIMEOUT = 60 * 15 # 15 minutes
FILL_CACHE = True
CACHE_EXPIRE_TIME = 60 * 60 * 4
@ -111,11 +118,6 @@ CATEGORIES_LIST_MAP = {
'dailys': []
}
SPONSORSHIP_COLOR_MAP = {
'super': (251, 140, 0),
'normal': (142, 36, 170)
}
# 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 = {

View File

@ -1,44 +1,94 @@
from sanic import Sanic
from sanic.log import logger
import ssl
from ssl import SSLContext
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from messages import ROOM_ERROR_TYPES
import smtplib
from messages import *
from config import SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD
from config import *
from jinja2 import Environment, FileSystemLoader
from threading import Timer, Lock
async def send_unconfirm_message (room_order, orders):
memberMessages = []
def killSmptClient():
global sslLock
global sslTimer
global smptSender
sslTimer.cancel()
sslLock.acquire()
if(smptSender is not None):
logger.debug('[SMPT] Closing smpt client')
smptSender.quit() # it calls close() inside
smptSender = None
sslLock.release()
issues_plain = ""
issues_html = "<ul>"
async def openSmptClient():
global sslLock
global sslTimer
global sslContext
global smptSender
sslTimer.cancel()
sslLock.acquire()
if(smptSender is None):
logger.debug('[SMPT] Opening smpt client')
client : smtplib.SMTP = smtplib.SMTP(SMTP_HOST, SMTP_PORT)
client.starttls(context=sslContext)
client.login(SMTP_USER, SMTP_PASSWORD)
smptSender = client
sslLock.release()
sslTimer = createTimer()
sslTimer.start()
for err in room_order.room_errors:
if err in ROOM_ERROR_TYPES.keys():
issues_plain += f"{ROOM_ERROR_TYPES[err]}\n"
issues_html += f"<li>{ROOM_ERROR_TYPES[err]}</li>"
issues_html += "</ul>"
def createTimer():
return Timer(SMPT_CLIENT_CLOSE_TIMEOUT, killSmptClient)
sslLock : Lock = Lock()
sslTimer : Timer = createTimer()
sslContext : SSLContext = ssl.create_default_context()
smptSender : smtplib.SMTP = None
for member in orders:
plain_body = ROOM_UNCONFIRM_TEXT['plain'].format(member.name, room_order.room_name, issues_plain)
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")
message.attach(plain_text)
message.attach(html_text)
message['Subject'] = '[Furizon] Your room cannot be confirmed'
message['From'] = 'Furizon <no-reply@furizon.net>'
message['To'] = f"{member.name} <{member.email}>"
memberMessages.append(message)
async def sendEmail(message : MIMEMultipart):
await openSmptClient()
logger.debug(f"[SMPT] Sending mail {message['From']} -> {message['to']} '{message['Subject']}'")
sslLock.acquire()
smptSender.sendmail(message['From'], message['to'], message.as_string())
sslLock.release()
if len(memberMessages) == 0: return
async def send_unconfirm_message(room_order, orders):
memberMessages = []
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())
issues_plain = ""
issues_html = "<ul>"
for err in room_order.room_errors:
errId = err[1]
order = err[0]
orderStr = ""
if order is not None:
orderStr = f"{order}: "
if errId in ROOM_ERROR_TYPES.keys():
issues_plain += f"{orderStr}{ROOM_ERROR_TYPES[errId]}\n"
issues_html += f"<li>{orderStr}{ROOM_ERROR_TYPES[errId]}</li>"
issues_html += "</ul>"
for member in orders:
plain_body = ROOM_UNCONFIRM_TEXT['plain'].format(member.name, room_order.room_name, issues_plain)
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")
message.attach(plain_text)
message.attach(html_text)
message['Subject'] = f'[{EMAIL_SENDER_NAME}] Your room cannot be confirmed'
message['From'] = f'{EMAIL_SENDER_NAME} <{EMAIL_SENDER_MAIL}>'
message['To'] = f"{member.name} <{member.email}>"
memberMessages.append(message)
if len(memberMessages) == 0: return
for message in memberMessages:
await sendEmail(message)
def render_email_template(title = "", body = ""):
tpl = Environment(loader=FileSystemLoader("tpl"), autoescape=False).get_template('email/comunication.html')
return str(tpl.render(title=title, body=body))
tpl = Environment(loader=FileSystemLoader("tpl"), autoescape=False).get_template('email/comunication.html')
return str(tpl.render(title=title, body=body))

View File

@ -18,7 +18,8 @@ def draw_profile (source, member, position, font, size=(170, 170), border_width=
# Draw border
idraw.rounded_rectangle(border_loc, border_width, border_color)
# Draw profile picture
with Image.open(f'res/propic/{member['propic'] or 'default.png'}') as to_add:
fileName = member['propic'] or 'default.png'
with Image.open(f'res/propic/{fileName}') as to_add:
source.paste(to_add.resize (size), profile_location)
name_len = idraw.textlength(str(member['name']), font)
calc_size = 0

View File

@ -94,21 +94,6 @@
#clock {display:block;}
</style>
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://y.foxo.me/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '2']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->
</head>
<body>
<div class="containedBg"></div>

View File

@ -3,4 +3,5 @@ sanic-ext
httpx
Pillow
aztec_code_generator
jinja2
Jinja2
Requests

3
startup.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/bash
python3 app.py 2>&1 | tee -a log.txt

44
stuff/runBackup.py Normal file
View File

@ -0,0 +1,44 @@
#!/usr/bin/python
from os import listdir, remove
from os.path import isfile, join
import datetime
import subprocess
PRETIX_BACKUP = False
WEBINT_BACKUP = False
BACKUP_DIR_PRETIX = "/home/pretix/backups/"
BACKUP_DIR_WEBINT = "/home/webint/backups/"
MAX_FILE_NO = 14
COMMAND_PRETIX_POSTGRES = "pg_dump -F p pretix | gzip > %s" # Restore with psql -f %s
COMMAND_PRETIX_DATA = "tar -cf %s /var/pretix-data" # Restore with tar -xvf %s. To make .secret readable I used setfacl -m u:pretix:r /var/pretix-data/.secret
COMMAND_WEBINT = "tar -cf %s /home/webint/furizon_webint" # Restore with tar -xvf %s
def deleteOlder(path : str, prefix : str, postfix : str):
backupFileNames = sorted([f for f in listdir(path) if (isfile(join(path, f)) and f.startswith(prefix) and f.endswith(postfix))])
while(len(backupFileNames) > MAX_FILE_NO):
print(f"Removing {backupFileNames[0]}")
remove(join(path, backupFileNames[0]))
backupFileNames.pop(0)
def genFileName(prefix : str, postfix : str):
return prefix + "_" + datetime.datetime.now(datetime.UTC).strftime('%Y%m%d-%H%M%S') + "_" + postfix
def runBackup(prefix : str, postfix : str, path : str, command : str):
deleteOlder(path, prefix, postfix)
name = join(path, genFileName(prefix, postfix))
process = subprocess.Popen(command % name, shell=True)
process.wait()
if(PRETIX_BACKUP):
runBackup("pretix_postres", "backup.sql.gz", join(BACKUP_DIR_PRETIX, "postgres"), COMMAND_PRETIX_POSTGRES)
runBackup("pretix_data", "backup.tar.gz", join(BACKUP_DIR_PRETIX, "data"), COMMAND_PRETIX_DATA)
if(WEBINT_BACKUP):
runBackup("webint_full", "backup.tar.gz", BACKUP_DIR_WEBINT, COMMAND_WEBINT)

32
stuff/testEmail.py Normal file
View File

@ -0,0 +1,32 @@
import smtplib
import ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from secrets import *
SMTP_HOST = 'smtp.office365.com'
SMTP_USER = 'webservice@furizon.net'
SMTP_PORT = 587
plain_body = "Test aaaa"
plain_text = MIMEText(plain_body, "plain")
message = MIMEMultipart("alternative")
message.attach(plain_text)
message['Subject'] = '[Furizon] This is a test!'
message['From'] = 'Furizon <webservice@furizon.net>'
message['To'] = f"Luca Sorace <strdjn@gmail.com>"
print("Start")
context = ssl.create_default_context()
print("Context created")
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as sender:
print("Created sender obj")
sender.starttls(context=context)
print("Tls started")
sender.login(SMTP_USER, SMTP_PASSWORD)
print("Logged in")
print(sender.sendmail(message['From'], message['to'], message.as_string()))
print("Mail sent")

View File

@ -20,7 +20,7 @@
h1 img {max-height:1.4em;}
.notice {padding:0.8em;border-radius:3px;background:#e53935;color:#eee;}
.notice a {color:#eee;text-decoration:underline;}
.container {max-width:40em;padding:1em;box-sizing:border-box;}
.container {max-width:50em;padding:1em;box-sizing:border-box;}
td > a[role=button] {padding: 0.3em 0.7em;}
td {padding-left: 0.2em;padding-right: 0.2em;}
td > input[type=file] {margin:0;padding:0;}

View File

@ -10,13 +10,13 @@
.container { max-width: 40em; padding: 1em; margin: 0 auto; }
.title { font-size: 1.75em; margin-bottom: 1.2em; color: #e1e6eb; margin-top: 0; font-family: sans-serif; }
.main-content { margin-top: 0; font-style: normal; font-weight: 400; font-family: sans-serif;}
.con-logo { height:2em;}
.con-logo { height:3em;}
.link { text-decoration: none; background-color: #1095c1; color: #fff; padding: 1em; border-radius: 5px; font-weight: 600; }
</style>
</head>
<div class="body">
<div class="container">
<img src="https://reg.furizon.net/res/furizon.png" class="con-logo">
<img src="https://reg.furizon.net/res/furizon.png" alt="con_logo" title="con_logo" class="con-logo">
<div>
<h2 class="title">{{title}}</h2>
<p class="main-content">{{body}}</p>

View File

@ -88,8 +88,8 @@
<details id="shuttle">
<summary role="button"><img src="/res/icons/bus.svg" class="icon" />Shuttle</summary>
<p>This year we teamed up with VisitFiemme to take our guests to the convention.</p>
<p style="text-align:right;"><a href="{{LOCALES['shuttle_link_url'][locale]}}" target="_blank" role="button">Book now</a></p>
<p>This year, a shuttle service operated by the tourism company of Val di Fiemme will be available. The shuttle service will consist of a bus serving the convention, with scheduled stops at major airports and train stations. More informations <a href="https://furizon.net/furizon-overlord/furizon-overlord-shuttle-bus/">in the dedicated page.</a></p>
<p style="text-align:right;"><a href="{{LOCALES['shuttle_link_url'][locale]}}" target="_blank" role="button">Book now!</a></p>
</details>
<details id="barcard">

View File

@ -154,26 +154,32 @@ async def validate_rooms(request, rooms, om):
logger.info('Validating rooms...')
if not om: om = request.app.ctx.om
failed_rooms = []
# rooms_to_unconfirm is the room that MUST be unconfirmed, room_with_errors is a less strict set containing all rooms with kind-ish errors
rooms_to_unconfirm = []
room_with_errors = []
# Validate rooms
for order in rooms:
# returns tuple (room owner order, check, room error list, room members orders)
result = await check_room(request, order, om)
order = result[0]
if(len(order.room_errors) > 0):
room_with_errors.append(result)
check = result[1]
if check != None and check == False:
failed_rooms.append(result)
rooms_to_unconfirm.append(result)
# End here if no room has failed check
if len(failed_rooms) == 0:
if len(room_with_errors) == 0:
logger.info('[ROOM VALIDATION] Every room passed the check.')
return
logger.warning(f'[ROOM VALIDATION] Room validation failed for orders: %s', list(map(lambda rf: rf[0].code, failed_rooms)))
roomErrListSrts = []
for fr in room_with_errors:
for error in fr[0].room_errors:
roomErrListSrts.append(f"[ROOM VALIDATION] [ERR] Parent room: {fr[0].code} {'C' if fr[0].room_confirmed else 'N'} | Order {error[0] if error[0] else '-----'} with code {error[1]}")
logger.warning(f'[ROOM VALIDATION] Room validation failed for orders: \n%s', "\n".join(roomErrListSrts))
# Get confirmed rooms that fail validation
failed_confirmed_rooms = list(filter(lambda fr: (fr[0].room_confirmed == True), failed_rooms))
failed_confirmed_rooms = list(filter(lambda fr: (fr[0].room_confirmed == True), rooms_to_unconfirm))
if len(failed_confirmed_rooms) == 0:
logger.info('[ROOM VALIDATION] No rooms to unconfirm.')
@ -213,6 +219,7 @@ async def check_room(request, order, om=None):
# This is not needed anymore you buy tickets already
#if quotas.get_left(len(order.room_members)) == 0:
# raise exceptions.BadRequest("There are no more rooms of this size to reserve.")
allOk = True
bed_in_room = order.bed_in_room # Variation id of the ticket for that kind of room
for m in order.room_members:
@ -223,20 +230,27 @@ async def check_room(request, order, om=None):
# Room user in another room
if res.room_id != order.code:
room_errors.append('room_id_mismatch')
room_errors.append((res.code, 'room_id_mismatch'))
allOk = False
if res.status != 'paid':
room_errors.append('unpaid')
room_errors.append((res.code, 'unpaid'))
if res.bed_in_room != bed_in_room:
room_errors.append('type_mismatch')
room_errors.append((res.code, 'type_mismatch'))
if order.room_confirmed:
allOk = False
if res.daily:
room_errors.append('daily')
room_errors.append((res.code, 'daily'))
if order.room_confirmed:
allOk = False
room_members.append(res)
if len(room_members) != order.room_person_no and order.room_person_no != None:
room_errors.append('capacity_mismatch')
room_errors.append((None, 'capacity_mismatch'))
if order.room_confirmed:
allOk = False
order.set_room_errors(room_errors)
return (order, len(room_errors) == 0, room_members)
return (order, allOk, room_members)