Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](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.
Expand Down
339 changes: 332 additions & 7 deletions src/main/java/com/example/Main.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
package com.example;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import com.repo.AccountRepository;
import com.repo.DTO.AccountDTO;
import com.repo.DTO.MissionDTO;
import com.repo.MoonMissionRepository;

import javax.sql.DataSource;
import java.sql.*;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Scanner;
import java.util.regex.Pattern;

public class Main {

private final Scanner scanner = new Scanner(System.in);

static void main(String[] args) {
if (isDevMode(args)) {
DevDatabaseInitializer.start();
Expand All @@ -26,13 +36,328 @@ public void run() {
"as system properties (-Dkey=value) or environment variables.");
}

try (Connection connection = DriverManager.getConnection(jdbcUrl, dbUser, dbPass)) {
} catch (SQLException e) {
throw new RuntimeException(e);
DataSource ds = new SimpleDriverManagerDataSource(
System.getProperty("APP_JDBC_URL"),
System.getProperty("APP_DB_USER"),
System.getProperty("APP_DB_PASS")
);
Comment on lines +39 to +43
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Resolved configuration values are ignored - environment variables won't work.

The code resolves config with fallback to environment variables (lines 29-31) but then directly uses System.getProperty() instead of the resolved values. This breaks the intended fallback behavior.

         DataSource ds = new SimpleDriverManagerDataSource(
-                System.getProperty("APP_JDBC_URL"),
-                System.getProperty("APP_DB_USER"),
-                System.getProperty("APP_DB_PASS")
+                jdbcUrl,
+                dbUser,
+                dbPass
         );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
DataSource ds = new SimpleDriverManagerDataSource(
System.getProperty("APP_JDBC_URL"),
System.getProperty("APP_DB_USER"),
System.getProperty("APP_DB_PASS")
);
DataSource ds = new SimpleDriverManagerDataSource(
jdbcUrl,
dbUser,
dbPass
);
🤖 Prompt for AI Agents
In src/main/java/com/example/Main.java around lines 39 to 43, the DataSource is
constructed using System.getProperty(...) directly which ignores the previously
resolved configuration values (with environment-variable fallbacks on lines
29-31); replace the direct System.getProperty calls with the resolved variables
(e.g., jdbcUrl, dbUser, dbPass or whatever local names were computed earlier)
when constructing the SimpleDriverManagerDataSource so the fallback logic is
honored; ensure those resolved variables are final/visible in this scope and
used instead of calling System.getProperty again.




AccountRepository ac = new AccountRepository(ds);
MoonMissionRepository mmc = new MoonMissionRepository(ds);



boolean authorized = login(ac);


if (!authorized) {
System.out.println("Invalid username or password");
System.out.println("press 0 to exit");
while(true){
String exit = scanner.nextLine().trim();
if(exit.equals("0")){
return;
}
}
}


while(true) {
int option = promptMenu();

switch (option) {
case 1 -> listMissions(mmc);
case 2 -> getMission(mmc);
case 3 -> missionsCountYear(mmc);
case 4 -> createAccount(ac);
case 5 -> updatePassword(ac);
case 6 -> deleteAccount(ac);
case 0 -> {
return;
}

default -> System.out.println("Invalid choice.\n");
}
}



}

/**
* Prompts username and password, checks if the combination is present in accounts
*
* @return <code>true</code> if the name/password combo exists
* <code>false</code> if either name/password isn't present
*/
private boolean login(AccountRepository ac) {
System.out.println("Username: ");
String unm = scanner.nextLine();
System.out.println("Password: ");
String pw = scanner.nextLine();

return ac.matchCredentials(unm, pw);
}

/**
* Prompts the menu options and returns a user input integer
* @return integer >= 0
* @see #getValidInt(String)
*/
private int promptMenu(){
System.out.print("\n" +
"1) List moon missions (prints spacecraft names from `moon_mission`).\n" +
"2) Get a moon mission by mission_id (prints details for that mission).\n" +
"3) Count missions for a given year (prompts: year; prints the number of missions launched that year).\n" +
"4) Create an account (prompts: first name, last name, ssn, password; prints confirmation).\n" +
"5) Update an account password (prompts: user_id, new password; prints confirmation).\n" +
"6) Delete an account (prompts: user_id; prints confirmation).\n" +
"0) Exit.\n");

return getValidInt("Enter Choice: ");
}


/**
* Lists all spacecraft from the moon_mission table
*/
private void listMissions(MoonMissionRepository mmc){
List<MissionDTO> missions = mmc.getAllMissions();

for(MissionDTO mission : missions){
System.out.println(mission.spacecraft());
}
}


/**
* Prompts for a mission ID and prints its data is available
* @see #getValidInt(String)
*/
private void getMission(MoonMissionRepository mmc){
int id = getValidInt("Mission Id: ");

Optional<MissionDTO> mission = mmc.getMissionById(id);
if(mission.isPresent()) {
MissionDTO m = mission.get();
System.out.println(
"\nSpacecraft: " + m.spacecraft() +
"\nLaunch date: " + m.launchDate() +
"\nCarrier rocket: " + m.carrierRocket() +
"\nOperator: " + m.operator() +
"\nMission type: " + m.missionType() +
"\nOutcome: " + m.outcome());
}
else {
System.out.println("\nMission not found.");
}
}

/**
* Prompts for a year and prints how many rows in moon_mission was launched then
* @see #getValidInt(String)
*/
private void missionsCountYear(MoonMissionRepository mmc){
int year = getValidInt("Mission Year: ");

System.out.println("\nMissions in " + year + ": " + mmc.missionCount(year));

}

/**
* Gives a flow for creating a new account, asking for First and Last name, SSN and password
* default accountname is assigned if available and promted for if not
* @see #getValidName(String)
* @see #getValidSSN(String)
* @see #getValidPassword(String)
*/
private void createAccount(AccountRepository ac) {
String fn = getValidName("First Name: ");
String ln = getValidName("Last Name: ");
String ssn = getValidSSN("SSN: ");
String pw = getValidPassword("Password: ");

String accName; //Default accountname is first three letters of first and last name
if(fn.length() <= 3 && ln.length() <=3){ //if both first and last name are 3 or fewer letters
accName = fn + ln;} //accountname is both full combined

else if (fn.length() <= 3){ //if only first name is 3 or fewer letters
accName = fn + ln.substring(0, 2); //accountname is full first name and first 3 from last name
}
else if(ln.length() <= 3){ //if only last name is 3 or fewer letters
accName = fn.substring(0, 2) + ln; //accountname is first 3 from first name and full last name
}
else{ //if both are longer than 3 accountname follows default pattern
accName = fn.substring(0, 2) + ln.substring(0, 2);
}
Comment on lines +182 to +194
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Account name generation uses wrong substring length.

Comments say "first 3 letters" but substring(0, 2) returns only 2 characters. If the intent is 3 characters, use substring(0, 3).

-        if(fn.length() <= 3 && ln.length() <=3){    //if both first and last name are 3 or fewer letters
+        if(fn.length() <= 3 && ln.length() <= 3){   //if both first and last name are 3 or fewer letters
             accName = fn + ln;}                     //accountname is both full combined

         else if (fn.length() <= 3){                 //if only first name is 3 or fewer letters
-            accName = fn + ln.substring(0, 2);      //accountname is full first name and first 3 from last name
+            accName = fn + ln.substring(0, 3);      //accountname is full first name and first 3 from last name
         }
         else if(ln.length() <= 3){                  //if only last name is 3 or fewer letters
-           accName = fn.substring(0, 2) + ln;       //accountname is first 3 from first name and full last name
+           accName = fn.substring(0, 3) + ln;       //accountname is first 3 from first name and full last name
         }
         else{                                       //if both are longer than 3 accountname follows default pattern
-            accName = fn.substring(0, 2) + ln.substring(0, 2);
+            accName = fn.substring(0, 3) + ln.substring(0, 3);
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String accName; //Default accountname is first three letters of first and last name
if(fn.length() <= 3 && ln.length() <=3){ //if both first and last name are 3 or fewer letters
accName = fn + ln;} //accountname is both full combined
else if (fn.length() <= 3){ //if only first name is 3 or fewer letters
accName = fn + ln.substring(0, 2); //accountname is full first name and first 3 from last name
}
else if(ln.length() <= 3){ //if only last name is 3 or fewer letters
accName = fn.substring(0, 2) + ln; //accountname is first 3 from first name and full last name
}
else{ //if both are longer than 3 accountname follows default pattern
accName = fn.substring(0, 2) + ln.substring(0, 2);
}
String accName; //Default accountname is first three letters of first and last name
if(fn.length() <= 3 && ln.length() <= 3){ //if both first and last name are 3 or fewer letters
accName = fn + ln;} //accountname is both full combined
else if (fn.length() <= 3){ //if only first name is 3 or fewer letters
accName = fn + ln.substring(0, 3); //accountname is full first name and first 3 from last name
}
else if(ln.length() <= 3){ //if only last name is 3 or fewer letters
accName = fn.substring(0, 3) + ln; //accountname is first 3 from first name and full last name
}
else{ //if both are longer than 3 accountname follows default pattern
accName = fn.substring(0, 3) + ln.substring(0, 3);
}
🤖 Prompt for AI Agents
In src/main/java/com/example/Main.java around lines 182 to 194, the code and
comments intend to use the first three characters of first and/or last name but
call substring(0, 2) which returns only two characters; change all substring(0,
2) calls to substring(0, 3) so you extract three characters when the name length
is > 3, and keep the existing checks (fn.length() <= 3 / ln.length() <= 3) to
ensure substring(0, 3) is only called on sufficiently long strings.


//Check if the accountname exists
Optional<AccountDTO> nameCheck;

while(true) {
nameCheck = ac.getAccountByName(accName);

if(nameCheck.isEmpty()){ //if accountname is available continue
break;
}
else{
accName = getValidName("Account Name: "); //if not prompt for a new accountname and check again
//todo add help method for accountname
}
}

//Create and add account
AccountDTO newAccount = new AccountDTO(0, accName, pw, fn, ln, ssn);

ac.createAccount(newAccount);

if(ac.accountExists(accName)){
System.out.println("\nAccount created");
}
else{
System.out.println("\nAccount creation failed.");
}

}


/**
* Updates password after prompting for an ID and new password
* @see #getValidInt(String)
* @see #getValidPassword(String)
*/
private void updatePassword (AccountRepository ac) {
int id = getValidInt("User id: "); //todo add check if account is present

if(ac.accountExists(id)){
String newPassword = getValidPassword("New password: ");

ac.updatePassword(id, newPassword);

if(ac.matchCredentials(ac.getAccountByID(id).get().name(), newPassword)){
System.out.println("Password updated");
}
else {
System.out.println("Password update failed.");
}
}
else{
System.out.println("Account not found.");
}

}

/**
* Deletes account after prompting for an ID
* @see #getValidInt(String)
*/
private void deleteAccount(AccountRepository ac) {
int id = getValidInt("User id: ");

if(ac.accountExists(id)){
ac.deleteAccount(id);
if(!ac.accountExists(id)){
System.out.println("Account deleted");
}
else {
System.out.println("Account deletion failed.");
}
}
else {
System.out.println("Account not found.");
}
}

/**
* Help method to get a valid and positive integer from input
* @param prompt message to explain what is asked
* @return integer >= 0
*/
private int getValidInt(String prompt){
while(true){
try {
System.out.println("\n" + prompt);
int option = Integer.parseInt(scanner.nextLine());

if (option >= 0) {
return option;
}
else {
System.out.println("Please enter a positive integer.\n");
}
}
catch (NumberFormatException e){
System.out.println("Please enter a valid integer\n");
}
}
}

/**
* Help method to get a valid string that only contains letters from input
* @param prompt message to explain what is asked
* @return Capitalized string with only letters
*/
private String getValidName(String prompt){
while(true){
System.out.println("\n" + prompt);
String name = scanner.nextLine().trim();

if (name.isBlank()) {
System.out.println("\nCannot be blank");
}
else if(!Pattern.matches("^[a-zA-Z]+$", name)){
System.out.println("\nMust only contain letters");
}
else{
return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
}
}
}
Comment on lines +302 to +317
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Edge case: Single-character name produces empty suffix.

If name is exactly 1 character, name.substring(1) returns an empty string, resulting in just the uppercase first character. Consider if this is acceptable or if minimum length validation is needed.

             else{
-                return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
+                String capitalized = name.substring(0, 1).toUpperCase();
+                if (name.length() > 1) {
+                    capitalized += name.substring(1).toLowerCase();
+                }
+                return capitalized;
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private String getValidName(String prompt){
while(true){
System.out.println("\n" + prompt);
String name = scanner.nextLine().trim();
if (name.isBlank()) {
System.out.println("\nCannot be blank");
}
else if(!Pattern.matches("^[a-zA-Z]+$", name)){
System.out.println("\nMust only contain letters");
}
else{
return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
}
}
}
private String getValidName(String prompt){
while(true){
System.out.println("\n" + prompt);
String name = scanner.nextLine().trim();
if (name.isBlank()) {
System.out.println("\nCannot be blank");
}
else if(!Pattern.matches("^[a-zA-Z]+$", name)){
System.out.println("\nMust only contain letters");
}
else{
String capitalized = name.substring(0, 1).toUpperCase();
if (name.length() > 1) {
capitalized += name.substring(1).toLowerCase();
}
return capitalized;
}
}
}
🤖 Prompt for AI Agents
In src/main/java/com/example/Main.java around lines 302 to 317, the current
capitalization logic uses name.substring(1) which yields an empty string for
single-character names; change the method to explicitly handle single-character
names by checking if name.length() == 1 and returning name.toUpperCase() in that
case, otherwise continue to return name.substring(0,1).toUpperCase() +
name.substring(1).toLowerCase(); keep existing blank/letters validation messages
intact.


/**
* Help method to get a correctly formated SSN from input
* @param prompt message to explain what is asked
* @return String with YYMMDD-XXXX formatting
*/
private String getValidSSN(String prompt){
while(true){
System.out.println("\n" + prompt);
String ssn = scanner.nextLine().trim();

if (ssn.isBlank()) {
System.out.println("\nCannot be blank");
}
else if(!Pattern.matches("^\\d{6}-\\d{4}$", ssn)){
System.out.println("\nMust follow pattern YYMMDD-XXXX");
}
else {
return ssn;
}
}
}

/**
* Help method to get a valid password from input
* @param prompt message to explain what is asked
* @return string of at least 6 characters
*/
private String getValidPassword(String prompt){
while(true){
System.out.println("\n" + prompt);
String pw = scanner.nextLine();

if(pw.length() < 6){
System.out.println("Password must be at least 6 characters");
}
else{
return pw;
}
}
//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.
Expand Down
Loading
Loading