diff --git a/src/main/java/org/to/telegramfinalproject/Database/ConnectionDb.java b/src/main/java/org/to/telegramfinalproject/Database/ConnectionDb.java index b58f4c0..8d72664 100644 --- a/src/main/java/org/to/telegramfinalproject/Database/ConnectionDb.java +++ b/src/main/java/org/to/telegramfinalproject/Database/ConnectionDb.java @@ -7,7 +7,7 @@ import java.sql.SQLException; public class ConnectionDb { private static final String JDBC_URL = "jdbc:postgresql://localhost:5432/Telegram"; private static final String USERNAME = "postgres"; - private static final String PASSWORD = "Partow@1384"; + private static final String PASSWORD = "124postpass"; public ConnectionDb() { } diff --git a/src/main/java/org/to/telegramfinalproject/UI/IntroController.java b/src/main/java/org/to/telegramfinalproject/UI/IntroController.java new file mode 100644 index 0000000..5cd5306 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/IntroController.java @@ -0,0 +1,145 @@ +package org.to.telegramfinalproject.UI; + +import javafx.animation.TranslateTransition; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.StackPane; +import javafx.util.Duration; +import javafx.scene.shape.Circle; +import javafx.scene.paint.Color; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class IntroController { + @FXML private StackPane imagePane; + @FXML private Label caption; + @FXML private HBox dotContainer; + @FXML private Button startButton; + + private List images = new ArrayList<>(); + private List captions = new ArrayList<>(); + private int currentIndex = 0; + + private final int width = 200; + private final int height = 200; + + @FXML + public void initialize() { + loadSlides(); + setupDots(); + showSlide(currentIndex); + + startAutoSlide(); + } + + private void loadSlides() { + images.add(new Image(getClass().getResource("/org/to/telegramfinalproject/Images/telegram_icon.png").toExternalForm())); + captions.add("๐Ÿš€ The world's fastest messaging app.\nIt is free and secure."); + + images.add(new Image(getClass().getResource("/org/to/telegramfinalproject/Images/fast.png").toExternalForm())); + captions.add("๐Ÿš€ Telegram delivers messages \nfastest than any other application."); + + images.add(new Image(getClass().getResource("/org/to/telegramfinalproject/Images/free.png").toExternalForm())); + captions.add("๐ŸŽ Telegram is free forever. No ads.\nNo subscription fees."); + + images.add(new Image(getClass().getResource("/org/to/telegramfinalproject/Images/secure.png").toExternalForm())); + captions.add("๐Ÿ”’ Telegram keeps your messages \nsafe from hacker attacks."); + } + + private void setupDots() { + dotContainer.getChildren().clear(); + for (int i = 0; i < images.size(); i++) { + Circle dot = new Circle(5, Color.LIGHTGRAY); + dotContainer.getChildren().add(dot); + } + } + + private void updateDots() { + for (int i = 0; i < dotContainer.getChildren().size(); i++) { + Circle dot = (Circle) dotContainer.getChildren().get(i); + if (i == currentIndex) { + dot.setFill(getSlideColor(i)); + } else { + dot.setFill(Color.LIGHTGRAY); + } + } + } + + private Color getSlideColor(int index) { + return switch (index) { + case 0 -> Color.DODGERBLUE; // Blue for Telegram logo + case 1 -> Color.FIREBRICK; // Red slide + case 2 -> Color.GOLDENROD; // Yellow slide + case 3 -> Color.SEAGREEN; // Green slide + default -> Color.GRAY; + }; + } + + private void showSlide(int index) { + imagePane.getChildren().clear(); + + ImageView imageView = new ImageView(images.get(index)); + imageView.setFitWidth(180); + imageView.setFitHeight(180); + imageView.setPreserveRatio(true); + + // Clip the image into a circle + Circle clip = new Circle(90, 90, 80); // centerX, centerY, radius + imageView.setClip(clip); + + imagePane.getChildren().add(imageView); + caption.setText(captions.get(index)); + updateDots(); + } + + + private void startAutoSlide() { + Thread slider = new Thread(() -> { + while (true) { + try { + Thread.sleep(3000); + } catch (InterruptedException ignored) {} + + javafx.application.Platform.runLater(() -> { + currentIndex = (currentIndex + 1) % images.size(); + showSlide(currentIndex); + }); + } + }); + slider.setDaemon(true); + slider.start(); + } + + @FXML + private void handleStartMessaging(ActionEvent event) { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/login_view.fxml")); + Scene loginScene = new Scene(loader.load()); + + // Get the current stage and its dimensions + Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow(); + double currentWidth = stage.getWidth(); + double currentHeight = stage.getHeight(); + + // Set the new scene and apply the previous size + stage.setScene(loginScene); + stage.setWidth(currentWidth); + stage.setHeight(currentHeight); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/to/telegramfinalproject/UI/LoginController.java b/src/main/java/org/to/telegramfinalproject/UI/LoginController.java new file mode 100644 index 0000000..c26a534 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/LoginController.java @@ -0,0 +1,134 @@ +package org.to.telegramfinalproject.UI; + +import javafx.animation.PauseTransition; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.Parent; +import javafx.scene.control.*; +import javafx.stage.Stage; +import javafx.util.Duration; +import org.json.JSONObject; +import org.to.telegramfinalproject.Client.ClientConnection; +import org.to.telegramfinalproject.Database.userDatabase; +import org.to.telegramfinalproject.Models.User; +import org.to.telegramfinalproject.Security.PasswordHashing; + +import java.io.IOException; + +public class LoginController { + + @FXML private TextField usernameField; + + @FXML private PasswordField passwordField; + @FXML private TextField visiblePasswordField; + + @FXML private Button togglePasswordBtn; + @FXML private Label errorLabel; + + private ClientConnection connection; + private boolean passwordVisible = false; + + @FXML + public void initialize() { + // Sync hidden and visible password fields + visiblePasswordField.textProperty().bindBidirectional(passwordField.textProperty()); + + try { + connection = new ClientConnection("localhost", 12345); + } catch (Exception e) { + System.out.println("Could not connect to server: " + e.getMessage()); + } + } + + @FXML + private void togglePasswordVisibility() { + passwordVisible = !passwordVisible; + visiblePasswordField.setVisible(passwordVisible); + visiblePasswordField.setManaged(passwordVisible); + passwordField.setVisible(!passwordVisible); + passwordField.setManaged(!passwordVisible); + togglePasswordBtn.setText(passwordVisible ? "๐Ÿ‘" : "๐Ÿ‘"); + } + + @FXML + private void handleLogin() { + String username = usernameField.getText(); + String password = passwordField.getText(); + + // 1. Check for empty fields + if (username.isEmpty() || password.isEmpty()) { + showError("Please fill in all required fields."); + return; + } + + // 2. Check if username exists + userDatabase userDb = new userDatabase(); + if (!userDb.existsByUsername(username)) { + showError("This username doesnโ€™t exist."); + return; + } + + // 3. Check if password is correct + User user = userDb.findByUsername(username); + if (!PasswordHashing.verify(password, user.getPassword())) { + showError("Incorrect password."); + return; + } + + // 4. Attempt to send to server + try { + JSONObject request = new JSONObject(); + request.put("action", "login"); + request.put("user_id", JSONObject.NULL); + request.put("username", username); + request.put("password", password); + request.put("profile_name", JSONObject.NULL); + + if (connection != null) { + connection.send(request.toString()); + String responseStr = connection.receive(); + JSONObject response = new JSONObject(responseStr); + System.out.println("Status: " + response.getString("status")); + System.out.println("Message: " + response.getString("message")); + + // Simulate successful login (since main.fxml isnโ€™t ready) + Alert alert = new Alert(Alert.AlertType.INFORMATION, "Login successful!"); + alert.show(); + } + + } catch (Exception ex) { + showError("Unable to connect to server. Please try again later."); + } + } + + @FXML + private void switchToRegister() throws IOException { + switchScene("register_view.fxml"); + } + + private void switchScene(String fxmlFile) { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/" + fxmlFile)); + Parent root = loader.load(); + Stage stage = (Stage) usernameField.getScene().getWindow(); + stage.setScene(new Scene(root, 500, 700)); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + showError("Could not load scene: " + fxmlFile); + } + } + + private void showError(String message) { + errorLabel.setVisible(false); + errorLabel.setText(""); // Clear the label text + + PauseTransition pause = new PauseTransition(Duration.millis(50)); + pause.setOnFinished(event -> { + errorLabel.setText(message); + errorLabel.setVisible(true); + }); + pause.play(); + } +} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/UI/RegisterController.java b/src/main/java/org/to/telegramfinalproject/UI/RegisterController.java new file mode 100644 index 0000000..22bda84 --- /dev/null +++ b/src/main/java/org/to/telegramfinalproject/UI/RegisterController.java @@ -0,0 +1,160 @@ +package org.to.telegramfinalproject.UI; + +import javafx.animation.PauseTransition; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.stage.Stage; +import javafx.util.Duration; +import org.json.JSONObject; +import org.to.telegramfinalproject.Client.ClientConnection; +import org.to.telegramfinalproject.Database.userDatabase; + +import java.io.IOException; + +public class RegisterController { + + @FXML private TextField userIdField; + @FXML private TextField usernameField; + @FXML private TextField profileNameField; + + @FXML private PasswordField passwordField; + @FXML private TextField visiblePasswordField; + + @FXML private PasswordField confirmPasswordField; + @FXML private TextField visibleConfirmPasswordField; + + @FXML private Button togglePasswordBtn; + @FXML private Button toggleConfirmBtn; + + @FXML private Label errorLabel; + + private ClientConnection connection; + + private boolean passwordVisible = false; + private boolean confirmVisible = false; + + @FXML + public void initialize() { + // Sync password and confirm fields with their visible counterparts + visiblePasswordField.textProperty().bindBidirectional(passwordField.textProperty()); + visibleConfirmPasswordField.textProperty().bindBidirectional(confirmPasswordField.textProperty()); + + try { + connection = new ClientConnection("localhost", 12345); + } catch (Exception e) { + System.out.println("Could not connect to server: " + e.getMessage()); + } + } + + @FXML + private void togglePasswordVisibility() { + passwordVisible = !passwordVisible; + visiblePasswordField.setVisible(passwordVisible); + visiblePasswordField.setManaged(passwordVisible); + passwordField.setVisible(!passwordVisible); + passwordField.setManaged(!passwordVisible); + togglePasswordBtn.setText(passwordVisible ? "๐Ÿ‘" : "๐Ÿ‘"); + } + + @FXML + private void toggleConfirmPasswordVisibility() { + confirmVisible = !confirmVisible; + visibleConfirmPasswordField.setVisible(confirmVisible); + visibleConfirmPasswordField.setManaged(confirmVisible); + confirmPasswordField.setVisible(!confirmVisible); + confirmPasswordField.setManaged(!confirmVisible); + toggleConfirmBtn.setText(confirmVisible ? "๐Ÿ‘" : "๐Ÿ‘"); + } + + @FXML + private void handleRegister() { + String userID = userIdField.getText().trim(); + String username = usernameField.getText().trim(); + String profileName = profileNameField.getText().trim(); + String password = passwordField.getText(); + String confirmPass = confirmPasswordField.getText(); + + userDatabase userDb = new userDatabase(); + String passwordRegex = "\\b(?=[^\\s]*[A-Z])(?=[^\\s]*[a-z])(?=[^\\s]*\\d)(?=[^\\s]*[!@#$%^&*])[^\\s]{8,}\\b"; + + // 1. Empty fields + if (username.isEmpty() || profileName.isEmpty() || password.isEmpty() || confirmPass.isEmpty()) { + showError("Please fill in all required fields."); + return; + } + + // 2. Username exists + if (userDb.existsByUsername(username)) { + showError("This username is already taken."); + return; + } + + // 3. User ID exists (if entered) + if (!userID.isEmpty() && userDb.existsByUserId(userID)) { + showError("This user ID is already taken."); + return; + } + + // 4. Password mismatch + if (!password.equals(confirmPass)) { + showError("Passwords do not match."); + return; + } + + // 5. Weak password + if (!password.matches(passwordRegex)) { + showError("Password must be at least 8 characters, include a capital letter, a number, and a special character."); + return; + } + + // 6. Attempt registration + try { + JSONObject request = new JSONObject(); + request.put("action", "register"); + request.put("user_id", userID.isEmpty() ? JSONObject.NULL : userID); + request.put("username", username); + request.put("password", password); + request.put("profile_name", profileName); + connection.send(request.toString()); + + Alert alert = new Alert(Alert.AlertType.INFORMATION, "Registration successful!"); + alert.show(); + + } catch (Exception ex) { + showError("Failed to register. Please try again later."); + } + } + + @FXML + private void switchToLogin() throws IOException { + switchScene("login_view.fxml"); + } + + private void switchScene(String fxmlFile) { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/to/telegramfinalproject/" + fxmlFile)); + Parent root = loader.load(); + Stage stage = (Stage) usernameField.getScene().getWindow(); // Or any node from your current scene + stage.setScene(new Scene(root, 500, 700)); + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + showError("Could not load scene: " + fxmlFile); + } + } + + private void showError(String message) { + errorLabel.setVisible(false); + errorLabel.setText(""); // Clear the label text + + PauseTransition pause = new PauseTransition(Duration.millis(50)); + pause.setOnFinished(event -> { + errorLabel.setText(message); + errorLabel.setVisible(true); + }); + pause.play(); + } +} diff --git a/src/main/java/org/to/telegramfinalproject/UI/RegisterForm.java b/src/main/java/org/to/telegramfinalproject/UI/RegisterForm.java index 16dc03d..9bda6be 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/RegisterForm.java +++ b/src/main/java/org/to/telegramfinalproject/UI/RegisterForm.java @@ -81,20 +81,14 @@ public class RegisterForm { alert.show(); } else { - Alert alert = new Alert(Alert.AlertType.ERROR, "Password doesn't Strong enough"); + Alert alert = new Alert(Alert.AlertType.ERROR, "Password isn't Strong enough"); alert.show(); } - - - - }); backButton.setOnAction(e -> { switchScene("login_view.fxml"); }); - - } private void switchScene(String fxmlFile) { diff --git a/src/main/java/org/to/telegramfinalproject/UI/TelegramApplication.java b/src/main/java/org/to/telegramfinalproject/UI/TelegramApplication.java index 1119cb8..47a1970 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/TelegramApplication.java +++ b/src/main/java/org/to/telegramfinalproject/UI/TelegramApplication.java @@ -12,9 +12,9 @@ import java.io.IOException; public class TelegramApplication extends Application { @Override public void start(Stage stage) throws IOException { - FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("login_view.fxml")); - Scene scene = new Scene(fxmlLoader.load(), 320, 240); - stage.setTitle("Hello!"); + FXMLLoader fxmlLoader = new FXMLLoader(TelegramApplication.class.getResource("/org/to/telegramfinalproject/Intro.fxml")); + Scene scene = new Scene(fxmlLoader.load(), 1480, 820); + stage.setTitle("Telegram"); stage.setScene(scene); stage.show(); } @@ -22,5 +22,4 @@ public class TelegramApplication extends Application { launch(); } -} - +} \ No newline at end of file diff --git a/src/main/java/org/to/telegramfinalproject/UI/login_view.java b/src/main/java/org/to/telegramfinalproject/UI/login_view.java index a7235ee..c89ef01 100644 --- a/src/main/java/org/to/telegramfinalproject/UI/login_view.java +++ b/src/main/java/org/to/telegramfinalproject/UI/login_view.java @@ -25,7 +25,7 @@ public class login_view { }); registerButton.setOnAction(e -> { - switchScene("RegisterForm.fxml"); + switchScene("register_view.fxml"); }); } diff --git a/src/main/resources/init.sql b/src/main/resources/init.sql index 47eb518..188772f 100644 --- a/src/main/resources/init.sql +++ b/src/main/resources/init.sql @@ -55,13 +55,16 @@ CREATE TABLE IF NOT EXISTS groups ( description TEXT ); -CREATE TABLE IF NOT EXISTS group_members ( - group_id UUID REFERENCES groups(internal_uuid) ON DELETE CASCADE, - user_id UUID REFERENCES users(internal_uuid), +CREATE TABLE group_members ( + group_id UUID NOT NULL, + user_id UUID NOT NULL, + role TEXT CHECK (role IN ('owner', 'admin', 'member')) DEFAULT 'member', + permissions JSONB DEFAULT '{}'::jsonb, -- ู…ุฎุตูˆุต adminู‡ุง joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - role VARCHAR(20) DEFAULT 'member', -- member/admin/owner PRIMARY KEY (group_id, user_id) - ); +); + + CREATE TABLE IF NOT EXISTS channels ( channel_id VARCHAR(70) UNIQUE, internal_uuid UUID PRIMARY KEY,, @@ -73,12 +76,16 @@ CREATE TABLE IF NOT EXISTS channels ( ); -CREATE TABLE IF NOT EXISTS channel_subscribers ( - channel_id UUID REFERENCES channels(internal_uuid) ON DELETE CASCADE, - user_id UUID REFERENCES users(internal_uuid), + +CREATE TABLE channel_subscribers ( + channel_id UUID NOT NULL, + user_id UUID NOT NULL, + role TEXT CHECK (role IN ('owner', 'admin', 'subscriber')) DEFAULT 'subscriber', + permissions JSONB DEFAULT '{}'::jsonb, subscribed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (channel_id, user_id) - ); +); + --(Bouns) CREATE TABLE IF NOT EXISTS channel_admins ( diff --git a/src/main/resources/org/to/telegramfinalproject/Images/fast.png b/src/main/resources/org/to/telegramfinalproject/Images/fast.png new file mode 100644 index 0000000..372f48b Binary files /dev/null and b/src/main/resources/org/to/telegramfinalproject/Images/fast.png differ diff --git a/src/main/resources/org/to/telegramfinalproject/Images/free.png b/src/main/resources/org/to/telegramfinalproject/Images/free.png new file mode 100644 index 0000000..aa0d5d0 Binary files /dev/null and b/src/main/resources/org/to/telegramfinalproject/Images/free.png differ diff --git a/src/main/resources/org/to/telegramfinalproject/Images/secure.png b/src/main/resources/org/to/telegramfinalproject/Images/secure.png new file mode 100644 index 0000000..8388802 Binary files /dev/null and b/src/main/resources/org/to/telegramfinalproject/Images/secure.png differ diff --git a/src/main/resources/org/to/telegramfinalproject/Images/telegram-bg.png b/src/main/resources/org/to/telegramfinalproject/Images/telegram-bg.png deleted file mode 100644 index 72b7153..0000000 Binary files a/src/main/resources/org/to/telegramfinalproject/Images/telegram-bg.png and /dev/null differ diff --git a/src/main/resources/org/to/telegramfinalproject/Images/telegram_icon.png b/src/main/resources/org/to/telegramfinalproject/Images/telegram_icon.png new file mode 100644 index 0000000..d955219 Binary files /dev/null and b/src/main/resources/org/to/telegramfinalproject/Images/telegram_icon.png differ diff --git a/src/main/resources/org/to/telegramfinalproject/Intro.fxml b/src/main/resources/org/to/telegramfinalproject/Intro.fxml new file mode 100644 index 0000000..02ff71b --- /dev/null +++ b/src/main/resources/org/to/telegramfinalproject/Intro.fxml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + +