diff --git a/README.md b/README.md
index d20aaf9..7071577 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+[](https://classroom.github.com/a/339Lr3BJ)
### How the tests work (and Docker requirement)
This project ships with an end‑to‑end CLI integration test suite that uses Testcontainers to spin up a temporary MySQL database.
diff --git a/databas-jdbc-Ericthilen.zip b/databas-jdbc-Ericthilen.zip
new file mode 100644
index 0000000..f6b8aac
Binary files /dev/null and b/databas-jdbc-Ericthilen.zip differ
diff --git a/mvnw b/mvnw
old mode 100644
new mode 100755
diff --git a/pom.xml b/pom.xml
index 002ff66..ae56267 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,6 +51,11 @@
mysql
1.21.3
+
+ org.slf4j
+ slf4j-nop
+ 2.0.17
+
@@ -70,4 +75,4 @@
-
+
\ No newline at end of file
diff --git a/src/main/java/com/example/AccountRepository.java b/src/main/java/com/example/AccountRepository.java
new file mode 100644
index 0000000..20fe9dd
--- /dev/null
+++ b/src/main/java/com/example/AccountRepository.java
@@ -0,0 +1,97 @@
+package com.example;
+
+import javax.sql.DataSource;
+import java.sql.*;
+
+public class AccountRepository {
+ private final DataSource ds;
+
+ public AccountRepository(DataSource ds) {
+ this.ds = ds;
+ }
+
+ /** Kontrollera login mot name + password */
+ public boolean login(String username, String password) throws SQLException {
+ String sql = "SELECT 1 FROM account WHERE name=? AND password=?";
+ try (Connection conn = ds.getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setString(1, username);
+ ps.setString(2, password);
+ try (ResultSet rs = ps.executeQuery()) {
+ return rs.next();
+ }
+ }
+ }
+
+ /** Skapa nytt konto och generera unikt username (name). Vid kollision läggs siffersuffix till (t.ex. AngFra1). */
+ public String createAccount(String first, String last, String ssn, String password) throws SQLException {
+ String base = (first == null ? "" : first.trim());
+ String sur = (last == null ? "" : last.trim());
+ String ssnTrim = (ssn == null ? "" : ssn.trim());
+
+ String baseName = base.substring(0, Math.min(3, base.length()))
+ + sur.substring(0, Math.min(3, sur.length()));
+
+ String sql = "INSERT INTO account(name, password, first_name, last_name, ssn) VALUES (?,?,?,?,?)";
+
+ try (Connection conn = ds.getConnection()) {
+ conn.setAutoCommit(true);
+
+ String candidate = baseName;
+ int suffix = 0;
+ while (true) {
+ try (PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
+ ps.setString(1, candidate);
+ ps.setString(2, password); // medvetet: lösenord trimmas inte
+ ps.setString(3, base);
+ ps.setString(4, sur);
+ ps.setString(5, ssnTrim);
+ ps.executeUpdate();
+
+ try (ResultSet keys = ps.getGeneratedKeys()) {
+ if (keys.next()) {
+ System.out.println("Account created with user_id=" + keys.getLong(1)
+ + " and username=" + candidate);
+ } else {
+ System.out.println("Account created with username=" + candidate);
+ }
+ }
+ return candidate;
+ } catch (SQLException e) {
+ // SQLState 23000 = integrity constraint violation (includes unique constraint)
+ String sqlState = e.getSQLState();
+ if ("23000".equals(sqlState) || e.getMessage().toLowerCase().contains("duplicate") || e.getMessage().toLowerCase().contains("unique")) {
+ suffix++;
+ candidate = baseName + suffix;
+ // prova igen med nytt kandidatnamn
+ continue;
+ }
+ throw e;
+ }
+ }
+ }
+ }
+
+ /** Uppdatera lösenord, returnerar true om lyckades */
+ public boolean updatePassword(long userId, String newPassword) throws SQLException {
+ String sql = "UPDATE account SET password=? WHERE user_id=?";
+ try (Connection conn = ds.getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setString(1, newPassword);
+ ps.setLong(2, userId);
+ int rows = ps.executeUpdate();
+ return rows > 0;
+ }
+ }
+
+ /** Ta bort konto, returnerar true om lyckades */
+ public boolean deleteAccount(long userId) throws SQLException {
+ String sql = "DELETE FROM account WHERE user_id=?";
+ try (Connection conn = ds.getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setLong(1, userId);
+ int rows = ps.executeUpdate();
+ return rows > 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/DevDatabaseInitializer.java b/src/main/java/com/example/DevDatabaseInitializer.java
index e8a45fe..5526da9 100644
--- a/src/main/java/com/example/DevDatabaseInitializer.java
+++ b/src/main/java/com/example/DevDatabaseInitializer.java
@@ -21,4 +21,4 @@ public static void start() {
System.setProperty("APP_DB_PASS", mysql.getPassword());
}
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/Main.java b/src/main/java/com/example/Main.java
index 6dc6fbd..5a8899a 100644
--- a/src/main/java/com/example/Main.java
+++ b/src/main/java/com/example/Main.java
@@ -1,13 +1,13 @@
package com.example;
-import java.sql.Connection;
-import java.sql.DriverManager;
+import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Arrays;
+import java.util.Scanner;
public class Main {
- static void main(String[] args) {
+ public static void main(String[] args) {
if (isDevMode(args)) {
DevDatabaseInitializer.start();
}
@@ -15,7 +15,7 @@ static void main(String[] args) {
}
public void run() {
- // Resolve DB settings with precedence: System properties -> Environment variables
+ // Konfigurera databas
String jdbcUrl = resolveConfig("APP_JDBC_URL", "APP_JDBC_URL");
String dbUser = resolveConfig("APP_DB_USER", "APP_DB_USER");
String dbPass = resolveConfig("APP_DB_PASS", "APP_DB_PASS");
@@ -26,32 +26,144 @@ public void run() {
"as system properties (-Dkey=value) or environment variables.");
}
- try (Connection connection = DriverManager.getConnection(jdbcUrl, dbUser, dbPass)) {
+ DataSource ds = new SimpleDriverManagerDataSource(jdbcUrl, dbUser, dbPass);
+ AccountRepository accountRepo = new AccountRepository(ds);
+ MoonMissionRepository missionRepo = new MoonMissionRepository(ds);
+
+ Scanner scanner = new Scanner(System.in);
+
+ System.out.println("Welcome to the Moon Mission CLI!");
+
+ // ---- Login flow: prompt for username/password first ----
+ String currentUser = null;
+ try {
+ System.out.print("Username: ");
+ String username = scanner.nextLine().trim();
+ System.out.print("Password: ");
+ String password = scanner.nextLine();
+
+ boolean validLogin = accountRepo.login(username, password);
+ if (!validLogin) {
+ System.out.println("Invalid username or password");
+ return; // stop program after invalid attempt (tests provide an extra 0 which will be ignored)
+ }
+ currentUser = username;
+ System.out.println("Login successful! Welcome, " + currentUser);
} catch (SQLException e) {
- throw new RuntimeException(e);
+ System.err.println("Database error: " + e.getMessage());
+ return;
+ } catch (Exception e) {
+ // In case input is exhausted
+ return;
}
- //Todo: Starting point for your code
+
+ // ---- Authenticated menu ----
+ boolean running = true;
+ while (running) {
+ System.out.println("\nMenu:");
+ System.out.println("1) List moon missions");
+ System.out.println("2) Get mission by id");
+ System.out.println("3) Count missions for year");
+ System.out.println("4) Create an account");
+ System.out.println("5) Update an account password");
+ System.out.println("6) Delete an account");
+ System.out.println("7) Log out (switch user)");
+ System.out.println("0) Exit");
+ System.out.print("Choose an option: ");
+
+ String choice = scanner.nextLine().trim();
+
+ try {
+ switch (choice) {
+ case "1":
+ missionRepo.listMissions(); // skriver direkt till konsolen
+ break;
+
+ case "2":
+ System.out.print("Mission ID: ");
+ long id = Long.parseLong(scanner.nextLine());
+ missionRepo.getMissionById(id); // skriver direkt till konsolen
+ break;
+
+ case "3":
+ System.out.print("Year: ");
+ int year = Integer.parseInt(scanner.nextLine());
+ missionRepo.countByYear(year); // skriver direkt till konsolen
+ break;
+
+ case "4":
+ System.out.print("First name: ");
+ String firstName = scanner.nextLine().trim();
+ System.out.print("Last name: ");
+ String lastName = scanner.nextLine().trim();
+ System.out.print("SSN: ");
+ String ssnNew = scanner.nextLine().trim();
+ System.out.print("Password: ");
+ String newPass = scanner.nextLine();
+ String newUsername = accountRepo.createAccount(firstName, lastName, ssnNew, newPass);
+ System.out.println("Account created. Username: " + newUsername);
+ break;
+
+ case "5":
+ System.out.print("User ID: ");
+ long uid = Long.parseLong(scanner.nextLine());
+ System.out.print("New password: ");
+ String passUpdate = scanner.nextLine();
+ boolean updated = accountRepo.updatePassword(uid, passUpdate);
+ System.out.println(updated ? "Password updated" : "Update failed");
+ break;
+
+ case "6":
+ System.out.print("User ID to delete: ");
+ long delId = Long.parseLong(scanner.nextLine());
+ boolean deleted = accountRepo.deleteAccount(delId);
+ System.out.println(deleted ? "Account deleted" : "Delete failed");
+ break;
+
+ case "7":
+ System.out.print("Username: ");
+ String newUser = scanner.nextLine().trim();
+ System.out.print("Password: ");
+ String newPassLogin = scanner.nextLine();
+ try {
+ boolean ok = accountRepo.login(newUser, newPassLogin);
+ if (ok) {
+ currentUser = newUser;
+ System.out.println("Login successful! Welcome, " + currentUser);
+ } else {
+ System.out.println("Invalid username or password");
+ }
+ } catch (SQLException e) {
+ System.err.println("Database error: " + e.getMessage());
+ }
+ break;
+
+ case "0":
+ running = false;
+ System.out.println("Exiting program...");
+ break;
+
+ default:
+ System.out.println("Invalid choice. Try again.");
+ }
+ } catch (SQLException e) {
+ System.err.println("Database error: " + e.getMessage());
+ } catch (NumberFormatException e) {
+ System.out.println("Invalid input format");
+ }
+ }
+
+ scanner.close();
}
- /**
- * Determines if the application is running in development mode based on system properties,
- * environment variables, or command-line arguments.
- *
- * @param args an array of command-line arguments
- * @return {@code true} if the application is in development mode; {@code false} otherwise
- */
private static boolean isDevMode(String[] args) {
- if (Boolean.getBoolean("devMode")) //Add VM option -DdevMode=true
+ if (Boolean.getBoolean("devMode"))
return true;
- if ("true".equalsIgnoreCase(System.getenv("DEV_MODE"))) //Environment variable DEV_MODE=true
+ if ("true".equalsIgnoreCase(System.getenv("DEV_MODE")))
return true;
- return Arrays.asList(args).contains("--dev"); //Argument --dev
+ return Arrays.asList(args).contains("--dev");
}
- /**
- * Reads configuration with precedence: Java system property first, then environment variable.
- * Returns trimmed value or null if neither source provides a non-empty value.
- */
private static String resolveConfig(String propertyKey, String envKey) {
String v = System.getProperty(propertyKey);
if (v == null || v.trim().isEmpty()) {
@@ -59,4 +171,4 @@ private static String resolveConfig(String propertyKey, String envKey) {
}
return (v == null || v.trim().isEmpty()) ? null : v.trim();
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/MoonMissionRepository.java b/src/main/java/com/example/MoonMissionRepository.java
new file mode 100644
index 0000000..2bea265
--- /dev/null
+++ b/src/main/java/com/example/MoonMissionRepository.java
@@ -0,0 +1,51 @@
+package com.example;
+
+import javax.sql.DataSource;
+import java.sql.*;
+
+public class MoonMissionRepository {
+ private final DataSource ds;
+
+ public MoonMissionRepository(DataSource ds) {
+ this.ds = ds;
+ }
+
+ public void listMissions() throws SQLException {
+ String sql = "SELECT spacecraft FROM moon_mission ORDER BY mission_id";
+ try (Connection conn = ds.getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql);
+ ResultSet rs = ps.executeQuery()) {
+ while (rs.next()) {
+ System.out.println(rs.getString("spacecraft"));
+ }
+ }
+ }
+
+ public void getMissionById(long id) throws SQLException {
+ String sql = "SELECT mission_id, spacecraft, launch_date FROM moon_mission WHERE mission_id=?";
+ try (Connection conn = ds.getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setLong(1, id);
+ try (ResultSet rs = ps.executeQuery()) {
+ if (rs.next()) {
+ System.out.println("Mission ID: " + rs.getLong("mission_id"));
+ System.out.println("Spacecraft: " + rs.getString("spacecraft"));
+ System.out.println("Launch Date: " + rs.getDate("launch_date"));
+ }
+ }
+ }
+ }
+
+ public void countByYear(int year) throws SQLException {
+ String sql = "SELECT COUNT(*) as count FROM moon_mission WHERE YEAR(launch_date)=?";
+ try (Connection conn = ds.getConnection();
+ PreparedStatement ps = conn.prepareStatement(sql)) {
+ ps.setInt(1, year);
+ try (ResultSet rs = ps.executeQuery()) {
+ if (rs.next()) {
+ System.out.println("Number of missions in " + year + ": " + rs.getInt("count"));
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/SimpleDriverManagerDataSource.java b/src/main/java/com/example/SimpleDriverManagerDataSource.java
new file mode 100644
index 0000000..cc9bcd7
--- /dev/null
+++ b/src/main/java/com/example/SimpleDriverManagerDataSource.java
@@ -0,0 +1,65 @@
+package com.example;
+
+import javax.sql.DataSource;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.logging.Logger;
+
+public class SimpleDriverManagerDataSource implements DataSource {
+ private final String url;
+ private final String user;
+ private final String pass;
+
+ public SimpleDriverManagerDataSource(String url, String user, String pass) {
+ this.url = url;
+ this.user = user;
+ this.pass = pass;
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ return DriverManager.getConnection(url, user, pass);
+ }
+
+ @Override
+ public Connection getConnection(String username, String password) throws SQLException {
+ return DriverManager.getConnection(url, username, password);
+ }
+
+ @Override
+ public PrintWriter getLogWriter() {
+ throw new UnsupportedOperationException("getLogWriter not supported");
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter out) {
+ throw new UnsupportedOperationException("setLogWriter not supported");
+ }
+
+ @Override
+ public void setLoginTimeout(int seconds) {
+ throw new UnsupportedOperationException("setLoginTimeout not supported");
+ }
+
+ @Override
+ public int getLoginTimeout() {
+ throw new UnsupportedOperationException("getLoginTimeout not supported");
+ }
+
+ @Override
+ public Logger getParentLogger() {
+ throw new UnsupportedOperationException("getParentLogger not supported");
+ }
+
+ @Override
+ public T unwrap(Class iface) {
+ throw new UnsupportedOperationException("unwrap not supported");
+ }
+
+ @Override
+ public boolean isWrapperFor(Class> iface) {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/test/com/example/CliAppIT.java b/src/test/com/example/com/example/CliAppIT.java
similarity index 99%
rename from src/test/com/example/CliAppIT.java
rename to src/test/com/example/com/example/CliAppIT.java
index 7d1eafb..5e4ffd4 100644
--- a/src/test/com/example/CliAppIT.java
+++ b/src/test/com/example/com/example/CliAppIT.java
@@ -338,4 +338,4 @@ private static boolean existsAccount(long userId) throws SQLException {
}
}
}
-}
+}
\ No newline at end of file