diff --git a/src/mathquiz/backend_1/EmailService.java b/src/mathquiz/backend_1/EmailService.java new file mode 100644 index 0000000..fe1f3c1 --- /dev/null +++ b/src/mathquiz/backend_1/EmailService.java @@ -0,0 +1,172 @@ +package mathquiz.backend; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; +import java.util.Random; + +/** + * Email Service Class - Handles email verification code sending + */ +public class EmailService { + + // Email configuration - loaded from properties file + private String smtpHost; + private String smtpPort; + private String emailUsername; + private String emailPassword; + private String fromEmail; + + public EmailService() { + loadEmailConfig(); + } + + /** + * Load email configuration from properties file + */ + private void loadEmailConfig() { + Properties config = new Properties(); + try { + config.load(new FileInputStream("email_config.properties")); + smtpHost = config.getProperty("smtp.host", "smtp.gmail.com"); + smtpPort = config.getProperty("smtp.port", "587"); + emailUsername = config.getProperty("email.username", "your-email@gmail.com"); + emailPassword = config.getProperty("email.password", "your-app-password"); + fromEmail = config.getProperty("email.from", "your-email@gmail.com"); + } catch (IOException e) { + System.err.println("Warning: Could not load email configuration, using default settings"); + // Use default settings + smtpHost = "smtp.gmail.com"; + smtpPort = "587"; + emailUsername = "your-email@gmail.com"; + emailPassword = "your-app-password"; + fromEmail = "your-email@gmail.com"; + } + } + + /** + * Send verification code to email + */ + public String sendVerificationCode(String email) throws Exception { + // Generate 6-digit numeric verification code + String verificationCode = generateVerificationCode(); + + // Send real email + sendRealEmail(email, verificationCode); + + // Also save to file for backup verification + saveVerificationCodeToFile(email, verificationCode); + + return verificationCode; + } + + /** + * Generate verification code + */ + private String generateVerificationCode() { + Random random = new Random(); + StringBuilder code = new StringBuilder(); + + for (int i = 0; i < 6; i++) { + code.append(random.nextInt(10)); + } + + return code.toString(); + } + + /** + * Send real email using SMTP protocol + */ + private void sendRealEmail(String email, String verificationCode) throws Exception { + try { + // Create email content + String subject = "Math Quiz System - Registration Verification Code"; + String content = createEmailContent(verificationCode); + + // Use SimpleEmailSender + SimpleEmailSender sender = new SimpleEmailSender( + smtpHost, + Integer.parseInt(smtpPort), + emailUsername, + emailPassword, + fromEmail + ); + + sender.sendEmail(email, subject, content); + + System.out.println("Verification code sent successfully to: " + email); + System.out.println("Verification code: " + verificationCode); + + } catch (Exception e) { + System.err.println("Failed to send email: " + e.getMessage()); + // Fall back to console output + System.out.println("=== EMAIL VERIFICATION CODE ==="); + System.out.println("To: " + email); + System.out.println("Subject: Math Quiz System - Registration Verification Code"); + System.out.println("Verification Code: " + verificationCode); + System.out.println("Please check your email or use the code above."); + System.out.println("================================"); + } + } + + + /** + * Create email content with HTML formatting + */ + private String createEmailContent(String verificationCode) { + return "" + + "" + + "" + + "
" + + "

Math Quiz System

" + + "

Registration Verification

" + + "

Hello,

" + + "

Thank you for registering with Math Quiz System. Please use the following verification code to complete your registration:

" + + "
" + + "

" + verificationCode + "

" + + "
" + + "

Important:

" + + "" + + "

Best regards,
Math Quiz System Team

" + + "
" + + "

This is an automated message, please do not reply.

" + + "
" + + "" + + ""; + } + + /** + * Save verification code to file (for demonstration only) + */ + private void saveVerificationCodeToFile(String email, String verificationCode) { + try { + java.nio.file.Files.createDirectories(java.nio.file.Paths.get("data")); + java.nio.file.Files.write( + java.nio.file.Paths.get("data/verification_codes.txt"), + (email + ":" + verificationCode + "\n").getBytes(), + java.nio.file.StandardOpenOption.CREATE, + java.nio.file.StandardOpenOption.APPEND + ); + } catch (Exception e) { + // Ignore file save errors + } + } + + /** + * Validate email format + */ + public boolean isValidEmail(String email) { + if (email == null || email.trim().isEmpty()) { + return false; + } + + String trimmedEmail = email.trim(); + return trimmedEmail.contains("@") && + trimmedEmail.contains(".") && + trimmedEmail.length() > 5; + } +} \ No newline at end of file diff --git a/src/mathquiz/backend_1/Question.java b/src/mathquiz/backend_1/Question.java new file mode 100644 index 0000000..7149e1b --- /dev/null +++ b/src/mathquiz/backend_1/Question.java @@ -0,0 +1,58 @@ +package mathquiz.backend; + +/** + * Question Class - Represents a multiple choice question + */ +public class Question { + private final String question; + private final String optionA; + private final String optionB; + private final String optionC; + private final String optionD; + private final String correctAnswer; + + public Question(String question, String optionA, String optionB, String optionC, String optionD, String correctAnswer) { + this.question = question; + this.optionA = optionA; + this.optionB = optionB; + this.optionC = optionC; + this.optionD = optionD; + this.correctAnswer = correctAnswer; + } + + public String getQuestion() { + return question; + } + + public String getOptionA() { + return optionA; + } + + public String getOptionB() { + return optionB; + } + + public String getOptionC() { + return optionC; + } + + public String getOptionD() { + return optionD; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + @Override + public String toString() { + return "Question{" + + "question='" + question + '\'' + + ", optionA='" + optionA + '\'' + + ", optionB='" + optionB + '\'' + + ", optionC='" + optionC + '\'' + + ", optionD='" + optionD + '\'' + + ", correctAnswer='" + correctAnswer + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/src/mathquiz/backend_1/QuizService.java b/src/mathquiz/backend_1/QuizService.java new file mode 100644 index 0000000..d4dc996 --- /dev/null +++ b/src/mathquiz/backend_1/QuizService.java @@ -0,0 +1,298 @@ +package mathquiz.backend; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import mathquiz.AccountType; +import mathquiz.Deduplicator; +import mathquiz.GeneratorFactory; +import mathquiz.ProblemGenerator; + +/** + * Quiz Service Class - Handles question generation and quiz logic + */ +public class QuizService { + + /** + * Generate multiple choice questions + */ + public List generateQuestions(AccountType accountType, int count) { + try { + // Get question generator + ProblemGenerator generator = GeneratorFactory.getGenerator(accountType); + + // Generate original questions + List rawQuestions = generator.generate(count); + + // Deduplication processing - use default username if current user is null + String username = getCurrentUsername(); + if (username == null || username.trim().isEmpty()) { + username = "default_user"; + } + Deduplicator deduplicator = new Deduplicator(username); + List uniqueQuestions = deduplicator.ensureUnique(rawQuestions, count, generator); + + // Convert to multiple choice questions + List questions = new ArrayList<>(); + for (String questionText : uniqueQuestions) { + Question question = convertToMultipleChoice(questionText, accountType); + questions.add(question); + } + + return questions; + } catch (Exception e) { + System.err.println("Error in generateQuestions: " + e.getMessage()); + e.printStackTrace(); + return new ArrayList<>(); // Return empty list instead of null + } + } + + /** + * Convert expression questions to multiple choice questions + */ + private Question convertToMultipleChoice(String expression, AccountType accountType) { + try { + // Calculate correct answer + double correctAnswer = calculateExpression(expression, accountType); + + // Handle invalid answers + if (Double.isNaN(correctAnswer) || Double.isInfinite(correctAnswer)) { + System.out.println("Invalid answer for expression: " + expression + ", using 0.0"); + correctAnswer = 0.0; + } + + System.out.println("Expression: " + expression + ", Answer: " + correctAnswer); + + // Generate wrong options + List options = generateOptions(correctAnswer); + System.out.println("Generated " + options.size() + " wrong options: " + options); + + // Randomly arrange options + Random random = new Random(); + String[] optionTexts = new String[4]; + String correctAnswerText = ""; + + // Create a list with correct answer and wrong options + List allOptions = new ArrayList<>(); + allOptions.add(correctAnswer); + allOptions.addAll(options); + + System.out.println("All options before shuffle: " + allOptions); + + // Shuffle the options + Collections.shuffle(allOptions); + + System.out.println("All options after shuffle: " + allOptions); + + // Find the correct answer index after shuffling + int correctAnswerIndex = allOptions.indexOf(correctAnswer); + System.out.println("Correct answer index: " + correctAnswerIndex); + + for (int i = 0; i < 4; i++) { + optionTexts[i] = formatAnswer(allOptions.get(i)); + if (i == correctAnswerIndex) { + correctAnswerText = getOptionLetter(i); + } + } + + System.out.println("Final options: " + java.util.Arrays.toString(optionTexts)); + System.out.println("Correct answer: " + correctAnswerText); + + return new Question( + "Calculate: " + expression + " = ?", + optionTexts[0], optionTexts[1], optionTexts[2], optionTexts[3], + correctAnswerText + ); + } catch (Exception e) { + System.err.println("Error in convertToMultipleChoice: " + e.getMessage()); + e.printStackTrace(); + // Return a default question + return new Question( + "Calculate: 1 + 1 = ?", + "1", "2", "3", "4", + "B" + ); + } + } + + /** + * Calculate expression result + */ + private double calculateExpression(String expression, AccountType accountType) { + try { + // Handle special functions + String processedExpression = preprocessExpression(expression, accountType); + + // Simple expression calculation (in actual application should use more comprehensive expression parser) + return evaluateExpression(processedExpression); + } catch (Exception e) { + // If calculation fails, return a default value + return 0.0; + } + } + + /** + * Preprocess expression, handle special functions + */ + private String preprocessExpression(String expression, AccountType accountType) { + String result = expression; + + // Handle square root + result = result.replaceAll("sqrt\\(([^)]+)\\)", "Math.sqrt($1)"); + + // Handle square + result = result.replaceAll("\\(([^)]+)\\)\\^2", "($1) * ($1)"); + + // Handle trigonometric functions + result = result.replaceAll("sin\\(([^)]+)\\)", "Math.sin($1)"); + result = result.replaceAll("cos\\(([^)]+)\\)", "Math.cos($1)"); + result = result.replaceAll("tan\\(([^)]+)\\)", "Math.tan($1)"); + + // Handle operators + result = result.replace("×", "*"); + result = result.replace("÷", "/"); + + return result; + } + + /** + * Simple expression calculation + */ + private double evaluateExpression(String expression) { + try { + // Simplified expression calculation, only handle basic arithmetic operations + return evaluateSimpleExpression(expression); + } catch (Exception e) { + // If calculation fails, return 0 + return 0.0; + } + } + + /** + * Simplified expression calculation + */ + private double evaluateSimpleExpression(String expression) { + // Implement a simple expression calculator here + // For simplification, only handle basic number operations + try { + // Remove all spaces + expression = expression.replaceAll("\\s+", ""); + + // Handle parentheses + while (expression.contains("(")) { + int start = expression.lastIndexOf("("); + int end = expression.indexOf(")", start); + if (end == -1) break; + + String subExpr = expression.substring(start + 1, end); + double result = evaluateSimpleExpression(subExpr); + expression = expression.substring(0, start) + result + expression.substring(end + 1); + } + + // Handle multiplication and division + while (expression.contains("*") || expression.contains("/")) { + expression = processOperators(expression, new String[]{"*", "/"}); + } + + // Handle addition and subtraction + while (expression.contains("+") || (expression.contains("-") && !expression.startsWith("-"))) { + expression = processOperators(expression, new String[]{"+", "-"}); + } + + return Double.parseDouble(expression); + } catch (Exception e) { + return 0.0; + } + } + + /** + * Process operators + */ + private String processOperators(String expression, String[] operators) { + for (String op : operators) { + int index = expression.indexOf(op); + if (index > 0) { + // Find left operand + int leftStart = index - 1; + while (leftStart >= 0 && (Character.isDigit(expression.charAt(leftStart)) || expression.charAt(leftStart) == '.')) { + leftStart--; + } + leftStart++; + + // Find right operand + int rightEnd = index + 1; + while (rightEnd < expression.length() && (Character.isDigit(expression.charAt(rightEnd)) || expression.charAt(rightEnd) == '.')) { + rightEnd++; + } + + double left = Double.parseDouble(expression.substring(leftStart, index)); + double right = Double.parseDouble(expression.substring(index + 1, rightEnd)); + double result = 0; + + switch (op) { + case "+": result = left + right; break; + case "-": result = left - right; break; + case "*": result = left * right; break; + case "/": result = right != 0 ? left / right : 0; break; + } + + expression = expression.substring(0, leftStart) + result + expression.substring(rightEnd); + break; + } + } + return expression; + } + + /** + * Generate wrong options + */ + private List generateOptions(double correctAnswer) { + List options = new ArrayList<>(); + Random random = new Random(); + + // Generate 3 wrong options + for (int i = 0; i < 3; i++) { + double wrongAnswer; + do { + // Generate wrong answers near correct answer + double variation = (random.nextDouble() - 0.5) * Math.abs(correctAnswer) * 0.5; + wrongAnswer = correctAnswer + variation; + + // Ensure wrong answer is not 0 and different from correct answer + if (wrongAnswer == 0) { + wrongAnswer = random.nextDouble() * 10 + 1; + } + } while (Math.abs(wrongAnswer - correctAnswer) < 0.01); + + options.add(wrongAnswer); + } + + return options; + } + + /** + * Format answer + */ + private String formatAnswer(double answer) { + if (answer == (long) answer) { + return String.valueOf((long) answer); + } else { + return String.format("%.2f", answer); + } + } + + /** + * Get option letter + */ + private String getOptionLetter(int index) { + return String.valueOf((char) ('A' + index)); + } + + /** + * Get current username + */ + private String getCurrentUsername() { + return mathquiz.gui.QuizSession.getInstance().getCurrentUser(); + } +} \ No newline at end of file diff --git a/src/mathquiz/backend_1/SimpleEmailSender.java b/src/mathquiz/backend_1/SimpleEmailSender.java new file mode 100644 index 0000000..4c1c18a --- /dev/null +++ b/src/mathquiz/backend_1/SimpleEmailSender.java @@ -0,0 +1,184 @@ +package mathquiz.backend; + +import java.io.*; +import java.net.*; +import javax.net.ssl.*; + +/** + * Simple Email Sender using SMTP protocol + * This class provides a basic SMTP client implementation + */ +public class SimpleEmailSender { + + private String smtpHost; + private int smtpPort; + private String username; + private String password; + private String fromEmail; + + public SimpleEmailSender(String smtpHost, int smtpPort, String username, String password, String fromEmail) { + this.smtpHost = smtpHost; + this.smtpPort = smtpPort; + this.username = username; + this.password = password; + this.fromEmail = fromEmail; + } + + /** + * Send email using SMTP protocol with SSL/TLS support + */ + public void sendEmail(String toEmail, String subject, String content) throws Exception { + Socket socket = null; + BufferedReader reader = null; + PrintWriter writer = null; + + try { + System.out.println("Connecting to SMTP server: " + smtpHost + ":" + smtpPort); + + // Connect to SMTP server + socket = new Socket(smtpHost, smtpPort); + reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + writer = new PrintWriter(socket.getOutputStream(), true); + + // Read server greeting + String response = reader.readLine(); + System.out.println("Server greeting: " + response); + if (!response.startsWith("220")) { + throw new Exception("SMTP server error: " + response); + } + + // Send EHLO command + String hostname = InetAddress.getLocalHost().getHostName(); + if (hostname == null || hostname.isEmpty()) { + hostname = "localhost"; + } + writer.println("EHLO " + hostname); + response = reader.readLine(); + System.out.println("EHLO response: " + response); + if (!response.startsWith("250")) { + throw new Exception("EHLO failed: " + response); + } + + // Read additional EHLO responses + while (response.startsWith("250-")) { + response = reader.readLine(); + System.out.println("EHLO continuation: " + response); + } + + // Start TLS if supported + writer.println("STARTTLS"); + response = reader.readLine(); + System.out.println("STARTTLS response: " + response); + if (response.startsWith("220")) { + // Upgrade to SSL socket + SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); + SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket, smtpHost, smtpPort, true); + sslSocket.startHandshake(); + + // Update streams to use SSL socket + reader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream())); + writer = new PrintWriter(sslSocket.getOutputStream(), true); + + // Send EHLO again after TLS + writer.println("EHLO " + hostname); + response = reader.readLine(); + System.out.println("EHLO after TLS: " + response); + if (!response.startsWith("250")) { + throw new Exception("EHLO after TLS failed: " + response); + } + + // Read additional EHLO responses + while (response.startsWith("250-")) { + response = reader.readLine(); + System.out.println("EHLO continuation after TLS: " + response); + } + } + + // Authenticate using AUTH LOGIN + writer.println("AUTH LOGIN"); + response = reader.readLine(); + System.out.println("AUTH LOGIN response: " + response); + if (!response.startsWith("334")) { + throw new Exception("AUTH LOGIN failed: " + response); + } + + // Send username (base64 encoded) + String encodedUsername = java.util.Base64.getEncoder().encodeToString(username.getBytes()); + writer.println(encodedUsername); + response = reader.readLine(); + System.out.println("Username auth response: " + response); + if (!response.startsWith("334")) { + throw new Exception("Username authentication failed: " + response); + } + + // Send password (base64 encoded) + String encodedPassword = java.util.Base64.getEncoder().encodeToString(password.getBytes()); + writer.println(encodedPassword); + response = reader.readLine(); + System.out.println("Password auth response: " + response); + if (!response.startsWith("235")) { + throw new Exception("Password authentication failed: " + response); + } + + // Send MAIL FROM + writer.println("MAIL FROM:<" + fromEmail + ">"); + response = reader.readLine(); + System.out.println("MAIL FROM response: " + response); + if (!response.startsWith("250")) { + throw new Exception("MAIL FROM failed: " + response); + } + + // Send RCPT TO + writer.println("RCPT TO:<" + toEmail + ">"); + response = reader.readLine(); + System.out.println("RCPT TO response: " + response); + if (!response.startsWith("250")) { + throw new Exception("RCPT TO failed: " + response); + } + + // Send DATA + writer.println("DATA"); + response = reader.readLine(); + System.out.println("DATA response: " + response); + if (!response.startsWith("354")) { + throw new Exception("DATA failed: " + response); + } + + // Send email headers and content + writer.println("From: " + fromEmail); + writer.println("To: " + toEmail); + writer.println("Subject: " + subject); + writer.println("MIME-Version: 1.0"); + writer.println("Content-Type: text/html; charset=UTF-8"); + writer.println(); + writer.println(content); + writer.println("."); + + response = reader.readLine(); + System.out.println("Email send response: " + response); + if (!response.startsWith("250")) { + throw new Exception("Email sending failed: " + response); + } + + // Quit + writer.println("QUIT"); + response = reader.readLine(); + System.out.println("QUIT response: " + response); + + System.out.println("Email sent successfully to: " + toEmail); + + } catch (Exception e) { + System.err.println("Failed to send email: " + e.getMessage()); + e.printStackTrace(); + throw e; + } finally { + try { + if (writer != null) writer.close(); + if (reader != null) reader.close(); + if (socket != null) socket.close(); + } catch (IOException e) { + // Ignore cleanup errors + } + } + } +} diff --git a/src/mathquiz/backend_1/UserService.java b/src/mathquiz/backend_1/UserService.java new file mode 100644 index 0000000..c518f36 --- /dev/null +++ b/src/mathquiz/backend_1/UserService.java @@ -0,0 +1,209 @@ +package mathquiz.backend; + +import java.io.*; +import java.nio.file.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * User Service Class - Handles user registration, login, password management + */ +public class UserService { + private static final String USERS_FILE = "data/users.txt"; + private static final String PASSWORDS_FILE = "data/passwords.txt"; + + private Map userPasswords; + private Map userEmails; + + public UserService() { + loadUserData(); + } + + /** + * User registration + */ + public void registerUser(String email, String username) throws IOException { + if (userEmails.containsValue(email)) { + throw new IllegalArgumentException("This email has already been registered"); + } + + userEmails.put(username, email); + + // Save user data + saveUserData(); + } + + /** + * Set password + */ + public void setPassword(String email, String password) throws IOException { + if (!isValidPassword(password)) { + throw new IllegalArgumentException("Password does not meet requirements"); + } + + String username = getUsernameByEmail(email); + if (username == null) { + throw new IllegalArgumentException("User does not exist"); + } + + userPasswords.put(username, password); + savePasswordData(); + } + + /** + * User login + */ + public boolean login(String email, String password) { + String username = getUsernameByEmail(email); + if (username == null) { + return false; + } + + String storedPassword = userPasswords.get(username); + if (storedPassword == null) { + return false; + } + + if (Objects.equals(storedPassword, password)) { + // Set current user (use email for session) + mathquiz.gui.QuizSession.getInstance().setCurrentUser(email); + return true; + } + + return false; + } + + /** + * Change password + */ + public boolean changePassword(String username, String oldPassword, String newPassword) throws IOException { + if (!isValidPassword(newPassword)) { + throw new IllegalArgumentException("New password does not meet requirements"); + } + + String storedPassword = userPasswords.get(username); + if (!Objects.equals(storedPassword, oldPassword)) { + return false; + } + + userPasswords.put(username, newPassword); + savePasswordData(); + return true; + } + + /** + * Validate password format + */ + private boolean isValidPassword(String password) { + if (password.length() < 6 || password.length() > 10) { + return false; + } + + boolean hasUpperCase = false; + boolean hasLowerCase = false; + boolean hasDigit = false; + + for (char c : password.toCharArray()) { + if (Character.isUpperCase(c)) { + hasUpperCase = true; + } else if (Character.isLowerCase(c)) { + hasLowerCase = true; + } else if (Character.isDigit(c)) { + hasDigit = true; + } + } + + return hasUpperCase && hasLowerCase && hasDigit; + } + + /** + * Get username by email + */ + private String getUsernameByEmail(String email) { + for (Map.Entry entry : userEmails.entrySet()) { + if (entry.getValue().equals(email)) { + return entry.getKey(); + } + } + return null; + } + + /** + * Generate username + */ + private String generateUsername(String email) { + String baseName = email.split("@")[0]; + String username = baseName; + int counter = 1; + + while (userEmails.containsKey(username)) { + username = baseName + counter; + counter++; + } + + return username; + } + + /** + * Load user data + */ + private void loadUserData() { + userPasswords = new HashMap<>(); + userEmails = new HashMap<>(); + + try { + // Create data directory + Files.createDirectories(Paths.get("data")); + + // Load user email mapping + if (Files.exists(Paths.get(USERS_FILE))) { + for (String line : Files.readAllLines(Paths.get(USERS_FILE))) { + String[] parts = line.split(","); + if (parts.length == 2) { + userEmails.put(parts[0], parts[1]); + } + } + } + + // Load user passwords + if (Files.exists(Paths.get(PASSWORDS_FILE))) { + for (String line : Files.readAllLines(Paths.get(PASSWORDS_FILE))) { + String[] parts = line.split(","); + if (parts.length == 2) { + userPasswords.put(parts[0], parts[1]); + } + } + } + + } catch (IOException e) { + // If loading fails, use empty data + userPasswords = new HashMap<>(); + userEmails = new HashMap<>(); + } + } + + /** + * Save user data + */ + private void saveUserData() throws IOException { + try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(USERS_FILE))) { + for (Map.Entry entry : userEmails.entrySet()) { + writer.write(entry.getKey() + "," + entry.getValue()); + writer.newLine(); + } + } + } + + /** + * Save password data + */ + private void savePasswordData() throws IOException { + try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(PASSWORDS_FILE))) { + for (Map.Entry entry : userPasswords.entrySet()) { + writer.write(entry.getKey() + "," + entry.getValue()); + writer.newLine(); + } + } + } +} \ No newline at end of file