-
Notifications
You must be signed in to change notification settings - Fork 59
Feature/cli #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feature/cli #21
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,62 +1,162 @@ | ||
| package com.example; | ||
|
|
||
| import java.sql.Connection; | ||
| import java.sql.DriverManager; | ||
| import java.sql.SQLException; | ||
| import java.util.Arrays; | ||
| import com.example.db.SimpleDriverManagerDataSource; | ||
| import com.example.repo.*; | ||
|
|
||
| import javax.sql.DataSource; | ||
| import java.io.*; | ||
| import java.util.*; | ||
|
|
||
| public class Main { | ||
|
|
||
| static void main(String[] args) { | ||
| public static void main(String[] args) { | ||
| if (isDevMode(args)) { | ||
| DevDatabaseInitializer.start(); | ||
| } | ||
| new Main().run(); | ||
|
|
||
| try { | ||
| new Main().run(args); | ||
| } catch (Exception e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| public void run() { | ||
| // Resolve DB settings with precedence: System properties -> Environment variables | ||
| public void run(String[] args) throws Exception { | ||
| BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); | ||
| PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out), true); | ||
|
|
||
| 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"); | ||
|
|
||
| if (jdbcUrl == null || dbUser == null || dbPass == null) { | ||
| throw new IllegalStateException( | ||
| "Missing DB configuration. Provide APP_JDBC_URL, APP_DB_USER, APP_DB_PASS " + | ||
| "as system properties (-Dkey=value) or environment variables."); | ||
| throw new IllegalStateException("Missing database configuration"); | ||
| } | ||
|
|
||
| try (Connection connection = DriverManager.getConnection(jdbcUrl, dbUser, dbPass)) { | ||
| } catch (SQLException e) { | ||
| throw new RuntimeException(e); | ||
| DataSource ds = new SimpleDriverManagerDataSource(jdbcUrl, dbUser, dbPass); | ||
| AccountRepository accounts = new JdbcAccountRepository(ds); | ||
| MoonMissionRepository missions = new JdbcMoonMissionRepository(ds); | ||
|
|
||
| while (true) { | ||
| out.print("Username:"); | ||
| out.flush(); | ||
| String username = in.readLine(); | ||
| if (username == null) return; | ||
| if ("0".equals(username.trim())) return; | ||
|
|
||
| out.print("Password:"); | ||
| out.flush(); | ||
| String password = in.readLine(); | ||
| if (password == null) return; | ||
|
|
||
| if (accounts.authenticate(username, password).isPresent()) { | ||
| break; | ||
| } | ||
|
|
||
| out.println("Invalid username or password"); | ||
| out.println("0) Exit"); | ||
| } | ||
|
|
||
| while (true) { | ||
| out.println("1) List moon missions"); | ||
| out.println("2) Get a moon mission by mission_id"); | ||
| out.println("3) Count missions for a given year"); | ||
| out.println("4) Create an account"); | ||
| out.println("5) Update an account password"); | ||
| out.println("6) Delete an account"); | ||
| out.println("0) Exit"); | ||
|
|
||
| String choice = in.readLine(); | ||
| if (choice == null) return; | ||
|
|
||
| switch (choice.trim()) { | ||
| case "1" -> { | ||
| for (String name : missions.listSpacecraftNames()) { | ||
| out.println(name); | ||
| } | ||
| } | ||
| case "2" -> { | ||
| out.print("mission_id:"); | ||
| out.flush(); | ||
| long id = Long.parseLong(in.readLine()); | ||
| missions.getMissionAsMap(id) | ||
| .ifPresentOrElse( | ||
| m -> out.println(format(m)), | ||
| () -> out.println("not found") | ||
| ); | ||
| } | ||
| case "3" -> { | ||
| out.print("year:"); | ||
| out.flush(); | ||
| int year = Integer.parseInt(in.readLine()); | ||
| int count = missions.countByYear(year); | ||
| out.println(year + ": " + count); | ||
| } | ||
| case "4" -> { | ||
| out.print("first name:"); | ||
| out.flush(); | ||
| String first = in.readLine(); | ||
|
|
||
| out.print("last name:"); | ||
| out.flush(); | ||
| String last = in.readLine(); | ||
|
|
||
| out.print("ssn:"); | ||
| out.flush(); | ||
| String ssn = in.readLine(); | ||
|
|
||
| out.print("password:"); | ||
| out.flush(); | ||
| String pw = in.readLine(); | ||
|
|
||
| long id = accounts.createAccount(first, last, ssn, pw); | ||
| out.println("account created " + id); | ||
| } | ||
| case "5" -> { | ||
| out.print("user_id:"); | ||
| out.flush(); | ||
| long id = Long.parseLong(in.readLine()); | ||
|
|
||
| out.print("new password:"); | ||
| out.flush(); | ||
| String pw = in.readLine(); | ||
|
|
||
| accounts.updatePassword(id, pw); | ||
| out.println("password updated"); | ||
| } | ||
| case "6" -> { | ||
| out.print("user_id:"); | ||
| out.flush(); | ||
| long id = Long.parseLong(in.readLine()); | ||
|
|
||
| accounts.deleteAccount(id); | ||
| out.println("account deleted"); | ||
| } | ||
| case "0" -> { | ||
| return; | ||
| } | ||
| default -> out.println("invalid option"); | ||
| } | ||
| } | ||
| //Todo: Starting point for your code | ||
| } | ||
|
|
||
| /** | ||
| * 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 String format(Map<String, Object> row) { | ||
| return row.entrySet().stream() | ||
| .sorted(Map.Entry.comparingByKey()) | ||
| .map(e -> e.getKey() + "=" + e.getValue()) | ||
| .reduce((a, b) -> a + " " + b) | ||
| .orElse(""); | ||
| } | ||
|
|
||
| private static boolean isDevMode(String[] args) { | ||
| if (Boolean.getBoolean("devMode")) //Add VM option -DdevMode=true | ||
| return true; | ||
| if ("true".equalsIgnoreCase(System.getenv("DEV_MODE"))) //Environment variable DEV_MODE=true | ||
| return true; | ||
| return Arrays.asList(args).contains("--dev"); //Argument --dev | ||
| if (Boolean.getBoolean("devMode")) return true; | ||
| if ("true".equalsIgnoreCase(System.getenv("DEV_MODE"))) return true; | ||
| return args != null && 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()) { | ||
| v = System.getenv(envKey); | ||
| } | ||
| return (v == null || v.trim().isEmpty()) ? null : v.trim(); | ||
| private static String resolveConfig(String prop, String env) { | ||
| String v = System.getProperty(prop); | ||
| if (v == null || v.isBlank()) v = System.getenv(env); | ||
| return (v == null || v.isBlank()) ? null : v.trim(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package com.example.db; | ||
|
|
||
| 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(); } | ||
| @Override public void setLogWriter(PrintWriter out) { throw new UnsupportedOperationException(); } | ||
| @Override public void setLoginTimeout(int seconds) { throw new UnsupportedOperationException(); } | ||
| @Override public int getLoginTimeout() { return 0; } | ||
| @Override public Logger getParentLogger() { throw new UnsupportedOperationException(); } | ||
| @Override public <T> T unwrap(Class<T> iface) { throw new UnsupportedOperationException(); } | ||
| @Override public boolean isWrapperFor(Class<?> iface) { return false; } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.example.repo; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| public interface AccountRepository { | ||
| Optional<Long> authenticate(String name, String password); | ||
| long createAccount(String firstName, String lastName, String ssn, String password); | ||
| void updatePassword(long userId, String newPassword); | ||
| void deleteAccount(long userId); | ||
| } | ||
|
Comment on lines
+5
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Repository boundary is clean, but password handling is plaintext end-to-end. 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| package com.example.repo; | ||
|
|
||
| import javax.sql.DataSource; | ||
| import java.sql.*; | ||
| import java.util.Optional; | ||
|
|
||
| public class JdbcAccountRepository implements AccountRepository { | ||
|
|
||
| private final DataSource ds; | ||
|
|
||
| public JdbcAccountRepository(DataSource ds) { | ||
| this.ds = ds; | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<Long> authenticate(String name, String password) { | ||
| String sql = "SELECT user_id FROM account WHERE name = ? AND password = ?"; | ||
| try (Connection c = ds.getConnection(); | ||
| PreparedStatement ps = c.prepareStatement(sql)) { | ||
| ps.setString(1, name); | ||
| ps.setString(2, password); | ||
| try (ResultSet rs = ps.executeQuery()) { | ||
| if (rs.next()) return Optional.of(rs.getLong(1)); | ||
| return Optional.empty(); | ||
| } | ||
| } catch (SQLException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public long createAccount(String firstName, String lastName, String ssn, String password) { | ||
| String name = makeUsername(firstName, lastName); | ||
| String sql = "INSERT INTO account (name, first_name, last_name, ssn, password) VALUES (?, ?, ?, ?, ?)"; | ||
|
|
||
| try (Connection c = ds.getConnection(); | ||
| PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { | ||
| ps.setString(1, name); | ||
| ps.setString(2, firstName); | ||
| ps.setString(3, lastName); | ||
| ps.setString(4, ssn); | ||
| ps.setString(5, password); | ||
| ps.executeUpdate(); | ||
|
|
||
| try (ResultSet keys = ps.getGeneratedKeys()) { | ||
| if (keys.next()) return keys.getLong(1); | ||
| } | ||
|
|
||
| try (PreparedStatement ps2 = c.prepareStatement("SELECT user_id FROM account WHERE name = ?")) { | ||
| ps2.setString(1, name); | ||
| try (ResultSet rs = ps2.executeQuery()) { | ||
| if (rs.next()) return rs.getLong(1); | ||
| } | ||
| } | ||
|
|
||
| throw new IllegalStateException("Could not get new user id"); | ||
| } catch (SQLException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
Comment on lines
+31
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
- try (PreparedStatement ps2 = c.prepareStatement("SELECT user_id FROM account WHERE name = ?")) {
- ps2.setString(1, name);
- try (ResultSet rs = ps2.executeQuery()) {
- if (rs.next()) return rs.getLong(1);
- }
- }
-
- throw new IllegalStateException("Could not get new user id");
+ throw new IllegalStateException("Could not get generated user id");🤖 Prompt for AI Agents |
||
|
|
||
| @Override | ||
| public void updatePassword(long userId, String newPassword) { | ||
| String sql = "UPDATE account SET password = ? WHERE user_id = ?"; | ||
| try (Connection c = ds.getConnection(); | ||
| PreparedStatement ps = c.prepareStatement(sql)) { | ||
| ps.setString(1, newPassword); | ||
| ps.setLong(2, userId); | ||
| ps.executeUpdate(); | ||
| } catch (SQLException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void deleteAccount(long userId) { | ||
| String sql = "DELETE FROM account WHERE user_id = ?"; | ||
| try (Connection c = ds.getConnection(); | ||
| PreparedStatement ps = c.prepareStatement(sql)) { | ||
| ps.setLong(1, userId); | ||
| ps.executeUpdate(); | ||
| } catch (SQLException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| private static String makeUsername(String first, String last) { | ||
| String f = first == null ? "" : first.trim(); | ||
| String l = last == null ? "" : last.trim(); | ||
| return cap(take3(f)) + cap(take3(l)); | ||
| } | ||
|
|
||
| private static String take3(String s) { | ||
| if (s.isEmpty()) return "XXX"; | ||
| return s.length() <= 3 ? s : s.substring(0, 3); | ||
| } | ||
|
|
||
| private static String cap(String s) { | ||
| if (s.isEmpty()) return s; | ||
| String low = s.toLowerCase(); | ||
| return Character.toUpperCase(low.charAt(0)) + low.substring(1); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard
readLine()+ numeric parsing to avoid hard-crashing on malformed input.Long.parseLong(in.readLine())andInteger.parseInt(in.readLine())will throw on null/blank/non-numeric input; consider re-prompting or printing “invalid option” / “invalid input”.🤖 Prompt for AI Agents