firt commit

This commit is contained in:
Kaby_Kun 2025-06-04 15:13:40 +02:00
commit c2e63830e1
71 changed files with 9613 additions and 0 deletions

29
server/utils/database.js Executable file
View file

@ -0,0 +1,29 @@
import mariadb from 'mariadb';
import dotenv from 'dotenv';
dotenv.config();
// Create a pool without database selection first
const initialPool = mariadb.createPool({
host: '172.10.1.4', // Remote database host
user: 'handball', // Remote database user
password: 'Gabi2104@', // Remote database password
database: 'handball', // Database name
connectionLimit: 5
});
export const pool = initialPool;
export async function query(sql, params) {
let conn;
try {
conn = await pool.getConnection();
const result = await conn.query(sql, params);
return result;
} catch (error) {
console.error('Database query error:', error);
throw error;
} finally {
if (conn) conn.release();
}
}

34
server/utils/email.js Executable file
View file

@ -0,0 +1,34 @@
import nodemailer from 'nodemailer';
import dotenv from 'dotenv';
dotenv.config();
// Create a transporter using SMTP
const transporter = nodemailer.createTransport({
host: 'mail.pandem.fr',
port: 465,
secure: true, // SSL
auth: {
user: 'datacenter@nazuna.ovh',
pass: '13,{,oCAlLaGENNiamoThFUllERpOrECriENI'
}
});
export async function sendEmail({ to, subject, text, attachments = [] }) {
try {
const mailOptions = {
from: '"HandBall Ticketer" <datacenter@nazuna.ovh>',
to,
subject,
text,
attachments
};
const info = await transporter.sendMail(mailOptions);
console.log('Email sent:', info.messageId);
return info;
} catch (error) {
console.error('Error sending email:', error);
throw error;
}
}

View file

@ -0,0 +1,47 @@
import nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({
host: 'mail.pandem.fr',
port: 465,
secure: true,
auth: {
user: 'datacenter@nazuna.ovh',
pass: '13,{,oCAlLaGENNiamoThFUllERpOrECriENI'
}
});
export const sendTicketEmail = async (ticketData, matchData, pdfPath) => {
try {
const mailOptions = {
from: '"HandBall Tickets" <datacenter@nazuna.ovh>',
to: ticketData.customerEmail,
subject: `Your Ticket for ${matchData.name}`,
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #2563eb;">Your Handball Match Ticket</h2>
<div style="background-color: #1f2937; color: white; padding: 20px; border-radius: 8px;">
<h3>${matchData.name}</h3>
<p><strong>Date:</strong> ${new Date(matchData.date).toLocaleString('fr-FR')}</p>
<p><strong>Location:</strong> ${matchData.location}</p>
<p><strong>Seat Number:</strong> ${ticketData.seatNumber}</p>
<p><strong>Ticket ID:</strong> ${ticketData.id}</p>
</div>
<p style="margin-top: 20px;">Thank you for your purchase! Your ticket is attached to this email.</p>
</div>
`,
attachments: [
{
filename: `ticket-${ticketData.id}.pdf`,
path: pdfPath
}
]
};
const info = await transporter.sendMail(mailOptions);
console.log('Email sent:', info.messageId);
return true;
} catch (error) {
console.error('Error sending email:', error);
throw error;
}
};

0
server/utils/fileStorage.js Executable file
View file

231
server/utils/initDatabase.js Executable file
View file

@ -0,0 +1,231 @@
import mariadb from 'mariadb';
// import dotenv from 'dotenv'; // Remove dotenv
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// dotenv.config(); // Remove dotenv config
// Create a pool using hardcoded database credentials (LESS SECURE)
const initialPool = mariadb.createPool({
host: '172.10.1.4',
user: 'handball',
password: 'Gabi2104@',
database: 'handball',
connectionLimit: 5
});
// Ensure required directories exist
const ensureDirectories = () => {
const uploadsDir = path.join(__dirname, '..', '..', 'public', 'uploads');
const pdfDir = path.join(__dirname, '..', '..', 'public', 'pdfs');
// Create uploads directory if it doesn't exist
if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(uploadsDir, { recursive: true });
console.log('Created uploads directory');
}
// Create pdfs directory if it doesn't exist
if (!fs.existsSync(pdfDir)) {
fs.mkdirSync(pdfDir, { recursive: true });
console.log('Created pdfs directory');
}
};
// Ensure .env file exists with required variables (This check is no longer strictly necessary but can be kept as a reminder)
const ensureEnvFile = () => {
const envPath = path.join(__dirname, '..' , '.env');
const requiredEnvVars = [
'DB_HOST',
'DB_USER',
'DB_PASSWORD',
'DB_NAME',
'PORT',
'SMTP_HOST',
'SMTP_PORT',
'SMTP_USER',
'SMTP_PASS'
];
if (!fs.existsSync(envPath)) {
console.warn('Warning: .env file not found. Using hardcoded credentials.'); // Change to warning
// Do not throw error, just warn
} else {
const envContent = fs.readFileSync(envPath, 'utf8');
const missingVars = requiredEnvVars.filter(varName => !envContent.includes(`${varName}=`)); // Check for key=value
if (missingVars.length > 0) {
console.warn('Warning: Missing required environment variables in .env:', missingVars.join(', ')); // Change to warning
}
}
};
// List of all required tables and their creation SQL
const tableDefinitions = {
matches: `
CREATE TABLE matches (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
date DATETIME NOT NULL,
location VARCHAR(255) NOT NULL,
totalSeats INT NOT NULL,
availableSeats INT NOT NULL,
price DECIMAL(10,2) DEFAULT 0,
timeoutDate DATETIME NOT NULL,
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`,
tickets: `
CREATE TABLE tickets (
id INT AUTO_INCREMENT PRIMARY KEY,
matchId INT NOT NULL,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
phone VARCHAR(20) NOT NULL,
seats INT NOT NULL,
status ENUM('pending', 'confirmed', 'cancelled') DEFAULT 'pending',
pdfFile VARCHAR(255),
deliveryMethod ENUM('download', 'email') DEFAULT 'download',
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_ticket_match FOREIGN KEY (matchId) REFERENCES matches(id) ON DELETE CASCADE
) /* Added ON DELETE CASCADE */
`,
seats: `
CREATE TABLE seats (
id INT AUTO_INCREMENT PRIMARY KEY,
matchId INT NOT NULL,
seatNumber INT NOT NULL,
status ENUM('available', 'reserved', 'booked') DEFAULT 'available',
ticketId INT NULL,
direction VARCHAR(50),
extractedSeatNumber INT,
uploadedPdfPath VARCHAR(255),
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_seat_match FOREIGN KEY (matchId) REFERENCES matches(id) ON DELETE CASCADE, /* Added ON DELETE CASCADE */
CONSTRAINT fk_seat_ticket FOREIGN KEY (ticketId) REFERENCES tickets(id) ON DELETE SET NULL, /* Added ON DELETE SET NULL */
UNIQUE KEY unique_seat_match (matchId, seatNumber)
)
`,
admin: `
CREATE TABLE admin (
id INT AUTO_INCREMENT PRIMARY KEY,
passwordHash VARCHAR(255) NOT NULL,
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) /* No foreign keys */
`,
admin_settings: `
CREATE TABLE admin_settings (
id INT AUTO_INCREMENT PRIMARY KEY,
setting_key VARCHAR(255) NOT NULL UNIQUE,
setting_value TEXT,
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) /* No foreign keys */
`
};
// Check if table exists
const tableExists = async (conn, tableName) => {
try {
const result = await conn.query(
`SELECT COUNT(*) as count FROM information_schema.tables
WHERE table_schema = ? AND table_name = ?`, // Use env var for schema
[process.env.DB_NAME, tableName]
);
return result[0].count > 0;
} catch (error) {
console.error(`Error checking if table ${tableName} exists:`, error);
return false;
}
};
// Create all tables
const createTables = async (conn) => {
try {
// Create tables in correct order to satisfy foreign keys
const orderedTableNames = ['matches', 'tickets', 'seats', 'admin', 'admin_settings'];
for (const tableName of orderedTableNames) {
const createSQL = tableDefinitions[tableName];
if (!createSQL) {
console.error(`Error: Table definition for ${tableName} not found.`);
continue;
}
try {
console.log(`Creating ${tableName} table...`);
await conn.query(createSQL);
console.log(`${tableName} table created successfully`);
} catch (error) {
// If table already exists, that's fine - just log it
if (error.code === 'ER_TABLE_EXISTS_ERROR') {
console.log(`${tableName} table already exists, skipping creation`);
} else {
// For any other error, rethrow it
throw error;
}
}
}
} catch (error) {
console.error('Error creating tables:', error);
throw error;
}
};
// Export the initialPool (Keep this export)
export { initialPool };
async function initDatabase() {
try {
// Check required directories and files
ensureDirectories();
ensureEnvFile(); // Keep the check but it won't stop execution
console.log('Attempting to connect to database server using hardcoded credentials...'); // Updated log
let conn;
try {
// Connect directly to the database using hardcoded credentials
conn = await initialPool.getConnection();
console.log('Connected to database server.');
// Check if the database exists (Keep this logic)
const dbCheck = await conn.query(
`SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?`,
['handball'] // Use hardcoded DB name here
);
if (dbCheck.length === 0) {
console.log(`Database 'handball' not found. Creating...`); // Updated log
// Create the database
await conn.query(`CREATE DATABASE handball`); // Use hardcoded DB name here
console.log(`Database 'handball' created.`); // Updated log
} else {
console.log(`Database 'handball' already exists.`); // Updated log
}
// Now that the database exists, ensure the connection is using it
// (Initial pool is already configured with hardcoded DB name)
console.log('Attempting to create tables (if they don\'t exist)...');
// Create tables if they don't exist
await createTables(conn);
console.log('Database initialization completed successfully');
} catch (error) {
console.error('Error during database initialization:', error);
throw error;
} finally {
if (conn) conn.release();
// The pool created with hardcoded DB name is needed for the main server
console.log('Database initialization complete, pool kept open for server using hardcoded credentials.'); // Updated log
}
} catch (error) {
console.error('Startup checks failed:', error);
throw error;
}
}
export default initDatabase;

74
server/utils/pdf.js Normal file
View file

@ -0,0 +1,74 @@
import PDFDocument from 'pdfkit';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export async function generateTicketPDF({ ticketId, match, name, email, phone, seats, pdfFile }) {
const doc = new PDFDocument({
size: 'A4',
margin: 50
});
const outputPath = path.join(__dirname, '../uploads', `ticket-${ticketId}.pdf`);
const writeStream = fs.createWriteStream(outputPath);
doc.pipe(writeStream);
// Add logo if exists
const logoPath = path.join(__dirname, '../assets/logo.png');
if (fs.existsSync(logoPath)) {
doc.image(logoPath, 50, 50, { width: 100 });
}
// Add ticket information
doc.fontSize(24).text('Handball Match Ticket', { align: 'center' });
doc.moveDown();
doc.fontSize(16).text(match.name, { align: 'center' });
doc.moveDown();
// Add match details
doc.fontSize(12);
doc.text(`Date: ${new Date(match.date).toLocaleString()}`);
doc.text(`Location: ${match.location}`);
// Add detailed seat information
doc.text('Seats:');
seats.forEach(seat => {
doc.text(` - ${seat.direction || 'Seat'} ${seat.extractedSeatNumber || seat.seatNumber}`);
});
doc.moveDown();
// Add customer details
doc.text('Customer Information:');
doc.text(`Name: ${name}`);
doc.text(`Email: ${email}`);
doc.text(`Phone: ${phone}`);
doc.moveDown();
// Add ticket ID
doc.text(`Ticket ID: ${ticketId}`);
doc.moveDown();
// Add QR code or barcode if needed
// TODO: Add QR code generation
// Add terms and conditions
doc.fontSize(10);
doc.text('Terms and Conditions:', { underline: true });
doc.text('1. This ticket is non-refundable');
doc.text('2. Please arrive at least 30 minutes before the match');
doc.text('3. Present this ticket at the entrance');
// Finalize PDF
doc.end();
return new Promise((resolve, reject) => {
writeStream.on('finish', () => {
resolve(outputPath);
});
writeStream.on('error', reject);
});
}

View file

@ -0,0 +1,66 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Function to extract seat number from filename
export const extractSeatNumber = (filename) => {
// Try to find a number in the filename
const matches = filename.match(/\d+/);
return matches ? parseInt(matches[0], 10) : null;
};
// Function to find the correct PDF for a seat number
export const findPdfForSeat = async (matchId, seatNumber) => {
try {
// Get the match's PDF files from the database
const [match] = await query('SELECT pdfFiles FROM matches WHERE id = ?', [matchId]);
if (!match || !match.pdfFiles) return null;
const pdfFiles = JSON.parse(match.pdfFiles);
// First try to find a PDF with the seat number in the filename
for (const pdfFile of pdfFiles) {
const filename = path.basename(pdfFile);
const pdfSeatNumber = extractSeatNumber(filename);
if (pdfSeatNumber === seatNumber) {
return path.join(__dirname, '..', 'uploads', pdfFile);
}
}
// If no match found, return the first PDF (admin will need to manually match)
if (pdfFiles.length > 0) {
return path.join(__dirname, '..', 'uploads', pdfFiles[0]);
}
return null;
} catch (error) {
console.error('Error finding PDF for seat:', error);
return null;
}
};
// Function to store uploaded PDFs
export const storePdf = async (file, matchId) => {
try {
const uploadDir = path.join(__dirname, '..', 'uploads', matchId);
// Create directory if it doesn't exist
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
const filename = `${Date.now()}-${file.originalname}`;
const filepath = path.join(uploadDir, filename);
// Move the file to the upload directory
await fs.promises.writeFile(filepath, file.buffer);
return path.join(matchId, filename);
} catch (error) {
console.error('Error storing PDF:', error);
throw error;
}
};