firt commit
This commit is contained in:
commit
c2e63830e1
71 changed files with 9613 additions and 0 deletions
29
server/utils/database.js
Executable file
29
server/utils/database.js
Executable 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
34
server/utils/email.js
Executable 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;
|
||||
}
|
||||
}
|
47
server/utils/emailService.js
Normal file
47
server/utils/emailService.js
Normal 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
0
server/utils/fileStorage.js
Executable file
231
server/utils/initDatabase.js
Executable file
231
server/utils/initDatabase.js
Executable 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
74
server/utils/pdf.js
Normal 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);
|
||||
});
|
||||
}
|
66
server/utils/pdfHandler.js
Normal file
66
server/utils/pdfHandler.js
Normal 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;
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue