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]
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.
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.
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
Increasing the debt of a debtor: borrow
Increases the debt of a debtor by the amount entered.
Format: borrow [INDEX] AMOUNT
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
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.
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:
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 …
Examples:
-
filter friendly
Displays contacts with thefriendly
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.
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)
-
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)].