Project: Codii

[Codii] is a desktop address book application specially designed for debt collectors to manage debtors in a simple manner. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface).

Debt collectors can store information such as the amount owed, debt borrow date and debt cleared date in addition to debtor’s personal information.

Unique features such as an interest calculator help debt collectors manage debts more efficiently.

Codii is evolved from [AddressBook - Level 4] which is a desktop address book application used for teaching Software Engineering principles.

Code contributed: [Functional code] [Test code]

Enhancement Added: Login/Logout

External behavior


Start of Extract [from: User Guide]

Logging into the address book application using Command Line Interface (CLI) : login

Logs into the address book.
Format: login USERNAME PASSWORD

It is advisable to use the GUI login instead of the CLI login.
The current implementation for password masking in the CLI login is less sophisticated than the GUI login. This can lead to unexpected or incorrect behaviors such as inconsistent masking of password or being unable to log in when username and password are entered correctly.
To reduce unexpected behaviors, users are advised to add or delete characters from the end of the command only.
The CLI login option is available for convenience only given that we want the user to be able to accomplish tasks faster using CLI than a GUI.

Examples:

  • login userAcc_123 pa$$_Word!@#&

  • login batMan_111 (Batcave.327+-)

Sample account to log into address book:
Username: loanShark97
Password: hitMeUp123

Logging into the address book application using Graphic User Interface (GUI): login

Logs into the address book.
Format: login

1) Type login and press Enter in the command box. You should see the same welcome page as shown in Figure 1.3.
2) Enter username and password in the respective fields.
3) Press Enter or click the Log in button.

To return to the command box, click the Back to command box button.

loginView

Figure 1.3 : How the welcome screen looks like after login is entered in the command box

Sample account to log into address book:
Username: loanShark97
Password: hitMeUp123

Logging out of the address book application : logout

Logs out of the address book.
Format: logout

End of Extract


Justification

Debtor’s personal information will be stored in the address book. Hence, there is a need to limit access of the address book to debt collectors only.

Only help, exit and login commands can be entered before login. If other commands, including unknown commands are entered, the error message "Please log in first" will be displayed. This is because users should log in first before being able to use the app. Users can open a help window using the help command to look for further information on how to login if they need to. Users should also be able to exit the app without being logged in.

It also makes sense for the user to be able to log out without closing the app.

The decision to include the CLI version of the login feature is based on convenience. One-shot commands take a shorter amount of time to type compared to multi-step commands. Although password masking causes the CLI login feature to behave unexpectedly at times, it is necessary to prevent others from seeing the password that is typed. The CLI login works as expected if the user does not insert/delete characters from within the input.

Implementation


Start of Extract [from: Developer Guide]

Login/logout mechanism

Although there are two versions for login, the Graphic User Interface(GUI) version and the Command Line Interface(CLI) version, they both use the same login mechanism (see Figure 4.8.1 below). The GUI login is recommended over the CLI login because it has better password masking capabilities. This is because the password field in the GUI login is implemented using JavaFX 8’s PasswordField. The CLI login exists, despite the inconsistent password masking, to allow the user to log into the app faster since using a one-shot command is faster than a multi-step command. The bugs in the CLI login could be resolved by removing password masking. However, this would have security implications because the password is not concealed.

After a user logs in using either the CLI or GUI login, verification of the login information will take place in the Model component. The username and password are verified against the information stored in preferences.json.

The password is stored as a SHA-512 hash to conceal the actual password. The salt that is used to generate the hashed password is also stored. Since the stored hash cannot be converted back to the original password, the password that is entered by the user needs to be hashed and verified against the stored hash. Thus, the same salt needs to be used to generate a hash to match with the stored hash. If a different salt is used, then the generated hash will be different from the stored hash even if the password provided is correct.

After verifying that the username and password matches the information stored in preferences.json, an event is raised to notify the UI component of the user authentication result. If the user has successfully logged in, the UI component will display the person list, info panel and allow other commands (such as list, edit, `add, etc.) to be executed from the command box.

The activity diagram below, Figure 4.8.1, shows the overall flow of the command execution for both GUI and CLI login: image::LoginActivityDiagram.PNG[width="800"] Figure 4.8.1 : Activity diagram of how the login command works for both CLI and GUI version

When a user enters the logout command, the LogoutCommand class in the Logic component will call the ModelManager#logout() method. Two events will be raised: LoginAppRequestEvent and LogoutAppRequestEvent. LoginAppRequestEvent is to set the isLoggedIn variable in LoginCommand to false and LogoutAppRequestEvent is to set the isLoggedOut variable in LogoutCommand to true. Both events need to be raised to notify the UI component to go back to the welcome page and restrict the commands that are allowed to be executed. Upon logout, the command history and undo/redo stacks are cleared.

Design Considerations

Aspect: Implementation of password masking
Alternative 1(current choice): Use the unicode character 'BLACK CIRCLE' (●) for password masking
Pros: Other characters can be detected as password input, such as the asterisk character '*' which is commonly used for password masking.
Alternative 2: Use the asterisk character (*) for password masking
Pros: User may use the asterisk character when entering the password. The asterisk character will be ignored and will not show in the text field when it is entered. This is because the method that handles password masking ignores the character used to mask the password. Refer to the code snippet below for the implementation:

/*
 * mask password after the second whitespace and prevent the reading of the BLACK_CIRCLE after replacing
 * a character in the command box text field with a BLACK_CIRCLE
 */
if (numOfSpaces >= 2 && currentInput.charAt(currentInput.length() - 1) != ' '
        && currentInput.charAt(currentInput.length() - 1) != BLACK_CIRCLE) {
    maskPasswordInput(currentInput);
}

Cons: Although '*' is an invalid password character, the user may still use it while entering the password. Hence, if the user types '*', the cursor will remain at its original position. The user will be under the impression that the command box is not registering what was typed.

End of Extract


Enhancement Added: Borrow/Payback command

External behavior


Start of Extract [from: User Guide]

Increasing the debt of a debtor: borrow

Increases the debt of a debtor by the amount entered.
Format: borrow [INDEX] AMOUNT

  • Increases the debt and total debt of the debtor at the specified INDEX by AMOUNT. The index refers to the index number shown in the last person listing. The index must be a positive integer (e.g. 1, 2, 3, …​)

  • If no index is specified, the debt of the currently selected person is updated instead.

  • AMOUNT has to be in dollars and cents. For example: 500.50 which represents $500.50.

  • This command also sets the date repaid to NOT REPAID if the person previously fully repaid his/her debts.

Examples:

  • borrow 1 500
    Increases the debt of the 1st person by $500.

  • borrow 2 1000.10
    Increases the debt of the 2nd person by $1000.10.

  • list
    select 2
    borrow 234
    Increases the debt of the 2nd person by $234.

Decreasing the debt of a person: payback

Decreases the debt of a person by the amount entered.
Format: payback [INDEX] AMOUNT

  • Decreases the debt of the person at the specified INDEX by AMOUNT. The index refers to the index number shown in the last person listing. The index must be a positive integer (e.g. 1, 2, 3, …​)

  • If no index is specified, the debt of the currently selected person is updated instead.

  • AMOUNT has to be in dollars and cents. For example: 600.15 which represents $600.15.

  • AMOUNT repaid cannot be more than the debt owed by the person at the specifiec INDEX.

  • If a person fully repays his/her debts:

    • The person will be listed in the whitelist if he/she is not blacklisted.

    • The person’s date repaid will be set to the day that this command was entered.

    • If the person was in the overdue list, he/she would be removed.

Examples:

  • payback 1 500
    Decreases the debt of the 1st person by $500.

  • payback 2 1000.10
    Decreases the debt of the 2nd person by $1000.10.

  • list
    select 3
    payback 234
    Decreases the debt of the 3rd person by $234.

End of Extract


Justification

Should there be a need to adjust the debt of a person, the debt collector can use the borrow command to increase the debt by a specified amount and the payback command to deduct a specified amount. If the debt collector uses the edit command to update the debt of a person, he/she has to manually calculate the new total debt before entering the new debt into the address book. The borrow and payback command eliminates this hassle by doing the math for the user.

The whitespace character is used as the argument separator for both commands because it is more intuitive than special prefixes such as n/ which is used to denote the name of a person.

Implementation


Start of Extract [from: Developer Guide]

Borrow/payback command mechanism

The borrow command allows users to increase the debt of a person should he/she borrow more money. On the other hand, when a debtor repays a specified amount, the payback command is used to deduct that amount from his/her current debt. The BorrowCommand and PaybackCommand classes, which handle the updating of the Debt fields in a Person object, extend UndoableCommand so that both of these commands can be undone or redone if necessary.

These two commands require one compulsory argument which is the amount that is borrowed/paid back. Indicating the index (as listed in the person list panel on the left side of the application window) of the person who borrowed or paid back money is optional. If no index is specified, the command will be executed for the person that is currently selected in the person list. The arguments (index and amount borrowed/paid back) are separated by a whitespace instead of special prefixes (e.g. prefix n/ used for name). Hence, the String#split method is used to tokenize the input using a single whitespace as the delimiter. As seen in Figure 4.2.1, the tokenized inputs (index and amount borrowed) are then converted to their appropriate Object types and supplied as arguments to the BorrowCommand constructor. Input for the payback command is tokenized and supplied to the PaybackCommand constructor in the same manner.

The BorrowCommand and PaybackCommand are executed in LogicManager. BorrowCommand updates the debt, totalDebt and dateRepaid attributes in the target Person object through the ModelManager#addDebtToPerson() which calls AddressBook#addDebtToPerson(). A new Person object is then created with the updated debt and totalDebt amount. Also, the dateRepaid field is set to NOT REPAID. The target Person object is then replaced with this new Person object.

PaybackCommand updates the target person’s debt in the same manner as the BorrowCommand. However, only the debt attribute is updated. If the person has fully repaid his/her debts, the PaybackCommand will set the date repaid to the day the PaybackCommand was executed. The person will also be listed in the whitelist.

For the BorrowCommand, the DateBorrow field in the new Person object needs to be updated to match the DateBorrow field in the target Person object. This is because the date of creation of the Person object is assigned to the DateBorrow field when a Person object is created.

The following sequence diagram, Figure 4.2.1, shows further details of the interaction between the Logic and Model component when the borrow command is executed:

BorrowCommandSequenceDiagram

Figure 4.2.1 : Sequence diagram of how the borrow command works

The payback command works in a similar way to the borrow command.

Design Considerations

Aspect: Implementation of BorrowCommandParser and PaybackCommandParser
Alternative 1 (current choice): Tokenize arguments using String#split()
Pros: Easier to parse arguments using String#split method since there are no prefixes in the command input. It is also easier to validate the number of arguments entered by the user. This can be done through checking the length of the String array returned by String#split.
Alternative 2: Modify ArgumentTokenizer#tokenize() to tokenize arguments
Pros: Better modularity.
Cons: Requires modifications to ArgumentTokenizer#tokenize() since supplying whitespace as a prefix to the current ArgumentTokenizer#tokenize() method incorrectly tokenizes arguments.

For example:

Entered command: borrow 1 500
Prefix supplied to ArgumentTokenizer#tokenize method: " "
Outcome: prefix " " will be mapped to 1 500 in argMultimap. Index and amount borrowed are not separated.


Aspect: Updating Debt field
Alternative 1 (current choice): Create a new Person object, called editedPerson, by supplying the target ReadOnlyPerson object to constructor Person::new
Pros: Straightforward and simple to implement.
Cons: Debt class will need to have another constructor that takes in a Double parameter for simpler implementation of AddressBook#addDebtToPerson().
Alternative 2: Reusing the AddressBook#updatePerson() method
Pros: Do not have to write the method from scratch.
Cons: A new Person object still has to be created in order to edit the Debt field. Since Addressbook#updatePerson() only accepts ReadOnlyPerson objects as parameters, more code has to be written to convert the Person object to be a ReadOnlyPerson object.

End of Extract


Filter contacts by tags : filter

Filters contacts in the address book according to the tags specified.
Format: filter TAG1 TAG2 …​

  • Contacts which contain at least one of the tags specified will be shown in the list.

    • e.g. A person in the address book, Alex, has two tags: friendly and cooperative. When the command filter friendly is entered, Alex will be shown in the filtered list.

Examples:

  • filter friendly
    Displays contacts with the friendly tag.

  • filter tricky violent dishonest
    Displays contacts who have at least one of these three tags: tricky, violent, dishonest.

End of Extract


Justification

Tags can be a way of adding additional information to a contact. The filter command provides debt collectors a way to group debtors according to these additional information for various purposes.

For example, tags can be used to indicate a particular behavior of a debtor. A debt collection agency can filter debtors by the violent tag to get a list of debtors that are violent. Manpower can then be planned accordingly to ensure that debt collectors go in pairs to collect debts from debtors listed in the 'violent' list for safety reasons.

Implementation


Start of Extract [from: Developer Guide]

Filter by tags mechanism

The filter command allows the user to filter contacts by tags. Multiple tags can be entered. The command returns a list of contacts that match at least one of the tags that is specified by the user.

Since the filtered person list stored in the ModelManager class is of the type FilteredList<>, filtering of the list can be done easily using Java’s FilteredList#setPredicate() method. Hence, a PersonContainsTagPredicate is created to check if a Person object contains the tags of interest. The code snippet below shows how the PersonContainsTagPredicate is implemented to sieve out the relevant contacts. The Stream#anyMatch() method ensures that the filtered list contains persons who have at least one tag specified by the user.

/**
 * Evaluates this predicate on the given {@code person}. This predicate tests if a person contains at least one tag
 * from {@code tagKeywords}.
 * @return {@code true} if the person matches the predicate,
 * otherwise {@code false}
 */
@Override
public boolean test(ReadOnlyPerson person) {
    Set<Tag> tagList = person.getTags();
    for (Tag tag : tagList) {
        if (tagKeywords.stream().anyMatch(keyword -> StringUtil.containsWordIgnoreCase(tag.tagName, keyword))) {
            return true;
        }
    }
    return false;
}

The list is filtered and updated through ListObserver#updateCurrentFilteredList() in the seedu.addressbook.commons package, instead of ModelManager#updateFilteredPersonList(), so that it works across all lists (masterlist, blacklist, whitelist and overdue list).

Design Considerations

Aspect: Implementation of PersonContainsTagPredicate
Alternative 1 (current choice): Predicate returns true if at least one tag matches the list of tags specified by the user
Pros: More contacts will be shown to the user. The additional information may be useful to the user.
Cons: The user may find some of the filtered contacts irrelevant.
Alternative 2: Predicate returns true only if the person contains the exact tags that are specified by the user
Pros: Shows the most relevant results if the user wants to search for an exact match.
Cons: If the contacts have multiple tags and the user remembers just one of the tags wrongly, the filter command will return zero results.

End of Extract


Other contributions

  • Reduce information in person card (Pull request #239)

  • Improve welcome page (Pull request #211)

  • Add debt repayment progress bar (Pull requests #194, #210)

  • Improve info panel (Commit enhance info panel)

  • Help others through slack and forum (Issues #177 and #81/ Slack: fields not showing information and explaining the difference between git fetch and git pull)

Project: Modulus

[Modulus] is a web app to make module mapping more convenient for NUS students going overseas for exchange. This project was developed in pairs for [CP2106 Independent Software Development Project (Orbital)].