implement wihslist and secret santa

This commit is contained in:
tylen 2025-11-24 17:24:41 +02:00
parent 5051abc440
commit 06e146f1c4
5 changed files with 226 additions and 7 deletions

View File

@ -1,13 +1,15 @@
from enum import Enum from enum import Enum
import mysql.connector import mysql.connector
import os import os
import random
from mysql.connector import pooling from mysql.connector import pooling
STARTUP_TABLE_CREATION_QUERIES = { STARTUP_TABLE_CREATION_QUERIES = {
"users": """CREATE TABLE IF NOT EXISTS users ( "users": """CREATE TABLE IF NOT EXISTS users (
Name varchar(255), Name varchar(255),
Attendance bool, Attendance bool,
Password VARCHAR(2048) Password VARCHAR(2048),
WishListUrl VARCHAR(2048)
);""", );""",
"sessions": """CREATE TABLE IF NOT EXISTS sessions ( "sessions": """CREATE TABLE IF NOT EXISTS sessions (
Token VARCHAR(2048), Token VARCHAR(2048),
@ -19,6 +21,10 @@ STARTUP_TABLE_CREATION_QUERIES = {
Capacity INT, Capacity INT,
reservedBy varchar(255) reservedBy varchar(255)
);""", );""",
"santa": """CREATE TABLE IF NOT EXISTS santa (
Name varchar(255),
Santa varchar(255)
);""",
} }
INJECT_TABLE_CREATION_QUERIES = { INJECT_TABLE_CREATION_QUERIES = {
@ -55,6 +61,7 @@ class DBClient:
self.pool = self.create_pool() # Create a connection pool self.pool = self.create_pool() # Create a connection pool
self.initialize_database() self.initialize_database()
self.initialize_secret_santa() # Initialize Secret Santa
def validate_env_variables(self): def validate_env_variables(self):
if not self.db_server or not self.db_port or not self.password or not self.database: if not self.db_server or not self.db_port or not self.password or not self.database:
@ -78,6 +85,57 @@ class DBClient:
self.query(STARTUP_TABLE_CREATION_QUERIES['hosting']) self.query(STARTUP_TABLE_CREATION_QUERIES['hosting'])
self.query(INJECT_TABLE_CREATION_QUERIES['hosting']) self.query(INJECT_TABLE_CREATION_QUERIES['hosting'])
def initialize_secret_santa(self):
table_exists = self.query("SHOW TABLES LIKE 'santa';")
if not table_exists:
self.query(STARTUP_TABLE_CREATION_QUERIES['santa'])
count_query = self.query('SELECT COUNT(*) FROM santa;')
if count_query[0][0] > 0:
self.app.logger.warning('The santa table is not empty. No assignments will be made.')
return
attendees = self.query('SELECT Name FROM users WHERE Attendance = 1;')
if not attendees:
return
attendees = [user[0] for user in attendees]
couples = [("Тюлень", "Тюлениха"), ("Медведь", "Ксения")]
max_attempts = 1000
for attempt in range(max_attempts):
shuffled_attendees = attendees.copy()
random.shuffle(shuffled_attendees)
santa_assignments = {}
valid = True
for index, user in enumerate(shuffled_attendees):
prev_user = shuffled_attendees[index - 1]
next_user = shuffled_attendees[(index + 1) % len(shuffled_attendees)]
if any(user in couple and (prev_user in couple or next_user in couple) for couple in couples):
valid = False
break
santa_assignments[user] = prev_user
if valid:
break
else:
self.app.logger.warning('Could not find valid Santa assignments after multiple attempts.')
return
self.app.logger.info(f'Santa assignments: {santa_assignments}')
for user, santa in santa_assignments.items():
self.query('INSERT INTO santa (Name, Santa) VALUES (%s, %s);', (user, santa))
def query(self, query_str, params=None): def query(self, query_str, params=None):
max_retries = 3 max_retries = 3
for attempt in range(max_retries): for attempt in range(max_retries):
@ -87,7 +145,7 @@ class DBClient:
connection = self.pool.get_connection() connection = self.pool.get_connection()
with connection.cursor() as cursor: with connection.cursor() as cursor:
cursor.execute(query_str, params) cursor.execute(query_str, params)
if 'SELECT' in query_str: if 'SELECT' in query_str or 'SHOW' in query_str:
results = cursor.fetchall() results = cursor.fetchall()
self.app.logger.info(f'Query results: {results}') self.app.logger.info(f'Query results: {results}')
return results return results

View File

@ -166,6 +166,63 @@ def registerUserEndpoints(app, database):
return jsonify(success=False, message=str(e)), 500 return jsonify(success=False, message=str(e)), 500
@app.route('/users/wishlist', methods=['PUT'])
def update_wishlist():
data = request.json
token = data.get('token')
wishlist_url = data.get('wishlist')
if wishlist_url is None:
return jsonify(success=False, message="Wishlist URL is required"), 400
query_session = "SELECT * FROM sessions WHERE Token=%s"
try:
result = database.query(query_session, params=(token,))
if not result:
return jsonify(success=False, message="Token is invalid or expired"), 401
user_name = result[0][1]
wishlist_query = "UPDATE users SET WishListUrl = %s WHERE Name = %s"
update_result = database.query(wishlist_query, params=(wishlist_url, user_name))
return jsonify(success=True, message="WishListUrl updated successfully"), 200
except Exception as e:
return jsonify(success=False, message=str(e)), 500
@app.route('/users/santa', methods=['GET'])
def get_santainfo():
token = request.args.get('token')
if not token:
return jsonify(success=False, message="Token is required"), 400
query_session = "SELECT * FROM sessions WHERE Token=%s"
try:
result = database.query(query_session, params=(token,))
if not result:
return jsonify(success=False, message="Token is invalid or expired"), 401
user_name = result[0][1]
santa_query = "SELECT Name FROM santa WHERE Santa = %s"
santa_result = database.query(santa_query, params=(user_name,))
if not santa_result:
return jsonify(success=False, message=f"User's {user_name} Santa info not found"), 404
santa_to = santa_result[0][0]
wishlist_query = "SELECT WishListUrl FROM users WHERE Name = %s"
wishlist_result = database.query(wishlist_query, params=(santa_to,))
santa_info = {
"santa_to": santa_to,
"wishlist": wishlist_result[0][0]
}
return jsonify(success=True, santa_info=santa_info), 200
except Exception as e:
return jsonify(success=False, message=str(e)), 500

View File

@ -1,7 +1,36 @@
import CenteredContainer from "./ChildrenContainer"; import CenteredContainer from "./ChildrenContainer";
import useFetchUser from "../utils/fetchUser.tsx"
import type { SantaInfo } from "../types/index";
import { useEffect, useState } from "react";
import { useNotification } from "../NotificationContext.tsx";
function SecretSanta() { function SecretSanta() {
const { updateWishlist, getSantaInfo } = useFetchUser()
const [ santaInfo, setSantaInfo ] = useState<SantaInfo | null>(null)
const [ wishListUrl, setWishListUrl ] = useState('')
const notify = useNotification();
const fetchSecretSanta = async () => {
const santaInfoData = await getSantaInfo()
setSantaInfo(santaInfoData)
}
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
const updated = await updateWishlist(wishListUrl)
if (updated) {
notify('Вишлсит обновлен', 'success')
} else {
notify('Не удалось обновить вишлист, все вопросы к админу', 'error')
}
}
useEffect(() => {
fetchSecretSanta()
}, [])
return ( return (
<> <>
<CenteredContainer> <CenteredContainer>
@ -9,7 +38,29 @@ function SecretSanta() {
<p className="mainText"> <p className="mainText">
Тут вы сможете узнать кому вы дарите свой подарок, а так же увидеть его вишлист, если он его добавит. Вы тоже сможете добавить свой вишлист, если захотите, чтобы ваш санта его видел! Тут вы сможете узнать кому вы дарите свой подарок, а так же увидеть его вишлист, если он его добавит. Вы тоже сможете добавить свой вишлист, если захотите, чтобы ваш санта его видел!
<br/><br/> <br/><br/>
Таблица в производстве... Ожидайте к <b>середине-концу ноября</b> <h3>Добавить свой вишлист</h3>
<form onSubmit={handleSubmit}>
<div>
<label>
Ссылка на вишлист:
<input
type="url"
value={wishListUrl}
onChange={(e) => setWishListUrl(e.target.value)}
required
/>
</label>
</div>
<button type="submit">
{'Отправить'}
</button>
</form>
<p>Вы санта для Пятки: <b>{santaInfo?.santa_to}</b></p>
{santaInfo?.wishlist ? (
<h4>Пятка оставила вам <a href={santaInfo.wishlist}>вишлсит</a></h4>
): (
<h4>Пятка не оставила вам вишлист, используйте свое воображение. Либо ждите пока добавит....</h4>
)}
</p> </p>
</CenteredContainer> </CenteredContainer>
</> </>

View File

@ -15,3 +15,8 @@ export interface User {
attendance: boolean | null attendance: boolean | null
name: string name: string
} }
export interface SantaInfo {
santa_to: string,
wishlist: string
}

View File

@ -2,7 +2,7 @@ import { useCookies } from 'react-cookie';
import { API_URL } from '../constants/constants'; import { API_URL } from '../constants/constants';
import { hashPassword } from './hashPassword'; import { hashPassword } from './hashPassword';
import { useState } from 'react'; import { useState } from 'react';
import type { User } from '../types'; import type { User, SantaInfo } from '../types';
const useFetchUser = () => { const useFetchUser = () => {
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
@ -137,6 +137,54 @@ const useFetchUser = () => {
} }
} }
const updateWishlist = async (wishlistUrl: string): Promise<boolean> => {
const token = apiCookie.apiToken
try {
const response = await fetch(`${API_URL}/users/wishlist`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
token,
wishlist: wishlistUrl,
}),
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
if (!data.success) throw new Error(data.message);
return true; // Attendance updated successfully
} catch (error) {
console.error('Error updating wishlist:', error);
return false; // Attendance update failed
}
}
const getSantaInfo = async (): Promise<SantaInfo | null> => {
const token = apiCookie.apiToken
try {
const response = await fetch(`${API_URL}/users/santa?token=${encodeURIComponent(token)}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
if (!data.success) throw new Error(data.message);
return data.santa_info
} catch (error) {
console.error('Error retrieving attendance:', error);
return null
}
}
const updateAttendance = async (attendanceStatus: boolean): Promise<boolean> => { const updateAttendance = async (attendanceStatus: boolean): Promise<boolean> => {
const token = apiCookie.apiToken const token = apiCookie.apiToken
try { try {
@ -186,7 +234,7 @@ const useFetchUser = () => {
} }
return { userSet, passwordCreate, signUser, validToken, updateAttendance, getAttendance, isLoading, getAttendanceAll }; return { userSet, passwordCreate, signUser, validToken, updateAttendance, updateWishlist, getAttendance, isLoading, getAttendanceAll, getSantaInfo };
}; };
export default useFetchUser; export default useFetchUser;