Overview
CRM Book application aims to assist Salespeople by implementing the most important functionality of a CRM System, that is, managing their business contacts and clients. In a CRM, buisness contacts are seperated into two categories, Leads and Contacts. Companies are usually added as Contacts.
Some unique features of this CRM that benefits the individual Salesperson even more is an enhanced detection of duplicates through fields such as email and phone number, importing of a CSV file of leads that the salesperson may have bought through a mailing list or obtained through a tradeshow, sharing of posts about their company through LinkedIn, as well as seeing directions to their Lead’s office through Google Maps. It is important to note that these features all enhance the experience of the salesperson, and does not distract from the core functionality of managing their relationship with their leads and contacts.
The user interacts with CRM Book using a CLI, and it has a GUI created with JavaFX. It is written in Java.
Summary of contributions
-
Major enhancement: Post a Status Update onto LinkedIn
-
What it does: After a Salesperson has logged in to LinkedIn via the linkedin_login command, he will be able to post status updates via the linkedin_share command.
-
Justification: This feature improves CRM Book significantly in that it does not require the Salesperson to navigate away from CRM Book just to post a status update about their company. In the Sales and Marketing world, LinkedIn is used often to promote news articles and other related company marketing materials, and this feature reduces the friction generated by doing
-
Highlights: This enhancement required an in-depth research into encryption and storing of API Secrets in a native application. It also made use of LinkedIn’s OAuth2 authentication scheme as well as API be able to work. The implementation was challenging as it required the exploration of new APIs outside of the address book. The enhancement also required studying more about events and how they are handled, as well as handling different threads on Java FX, in particular handling the 'Not on FX application thread' exception.
-
Credits: LinkedIn API
-
-
Major enhancement: View directions to a customer or potential customer’s office via Google Maps
-
What it does: After a Salesperson has set their office location on the CRM Book, they will be able to view directions to a customer’s or potential customer’s office by clicking on their name.
-
Justification: This feature improves CRM Book significantly in that it does not require the Salesperson to navigate away from CRM Book just to find directions to their client’s location, or to know how long it will take to get there. Salespeople in general travel to other offices often to do a demo or to pitch a product. With this feature, the salesperson will not have to use their smartphone to view directions while still in the office. Rather, they will be able to view directions in the CRM Book and then leave the office when necessary.
-
Highlights: This enhancement involved learning more about Google Map’s webservice, studying between the different APIs and other options that they provided, as well as research on how to use the config.json file in the CRM Book.
-
Credits: Google Maps URLs
-
-
Code contributed: [Functional code] [Test code]
-
Other contributions:
-
Project management:
-
Managed releases
v1.2
-v1.5rc
(5 releases) on GitHub
-
-
Community:
-
Reported bugs and suggestions for other teams in the class (examples: Issue #157, Issue #132, Issue #133, Issue #134, Issue #136, Issue #139, Issue #140, Issue #141, Issue #145, Issue #149)
-
-
Contributions to the User Guide
Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users. |
A CRM System is a Customer Relationship Management system, mainly used in the real world by Sales and Marketing teams to manage their business-customer relationships, as well as to manage business contacts, employees, clients, contract wins and sales leads. Some examples of popular CRMs include Salesforce, SAP, and Microsoft Dynamics CRM.
A common complaint against such traditional CRMs is that they are large, contain too many functionalities, and in some cases even require additional training before using. (Search for: Salesforce Trailhead)
The CRM Book application aims to assist Salespeople by implementing the most important functionality of a CRM System, that is, managing their business contacts and clients. In a CRM, business contacts are seperated into two categories, Leads and Contacts. Leads are people that the salesperson has sold to in the past, while Contacts are people whom the salesperson has sold to before, even if the company they represent may no longer purchase the product. Also, accounts represent Companies.
Some unique features of this CRM in v1.5 that benefits the individual Salesperson even more is an enhanced detection of duplicates through fields such as email and phone number, importing of a CSV file of leads that the salesperson may have bought through a mailing list or obtained through a tradeshow, sharing of posts about their company through LinkedIn, as well as seeing directions to their Lead’s office through Google Maps. It is important to note that these features all enhance the experience of the salesperson, and does not distract from the core functionality of managing their relationship with their leads and contacts.
Future releases of the CRM Book would include the ability to schedule meetings through a Calendar, integration with other Social Media networks to post company related news, allowing a client to choose a meeting slot among a few that the salesperson has specified, and also manage the contracts that tie a Contact to an account.
More importantly, CRM Book is optimized for those who prefer to work with a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, CRM Book can get your contact management tasks done faster than traditional GUI apps.
Interested? Jump to the [Quick Start] to get started. Enjoy!
Log in to LinkedIn: linklog
, linkedin_login
Format: 'linklog', 'linkedin_login'
A LinkedIn Login is required if you want to share posts to linkedIn. If this is your first login, you will also have to give permissions for CRM Book in LinkedIn via a pop up. |
Share a Post on LinkedIn: linkshare
, linkedin_share
Format: 'linkshare [content]', 'linkedin_share [content]'
This command will allow you to share a post on LinkedIn to all your connections. This will allow you to share any interesting marketing materials your company may be involved in quickly. |
Setting current location: set_office_address
, setA
Sets the current office address for Google Maps
Format: `setA a/ADDRESS
Setting this address is required if you want to see the Google Map directions to a customer’s location |
Get directions to customer’s office: commandless
In order to use this, you must have an office address set. You can then either click on a person card or use the select
command in order to view the Google Maps directions.
If a person card was selected while the office location is set, you have to select a different person card to see the map |
Success of this command is also dependent on the success of Google intepreting the address entered |
Contributions to the Developer Guide
Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project. |
LinkedIn Login Feature
Current Implementation
The linkedInLogin mechanism is handled largely by the oAuth2Client interacting with the browser window, which the user will interact with in order to give LinkedIn their username, password, as well as granting the application permission for use.
When the linkedIn_login
command is called, a ShowBrowserRequestEvent
will be fired by LinkedInLoginCommand(Class)
and picked up by the MainWindow.
@Override
public CommandResult execute() {
EventsCenter.getInstance().post(new ShowBrowserRequestEvent());
return new CommandResult(MESSAGE_SUCCESS);
}
The MainWindow in turn will call the OAuth2Client(Class)
which will fire up the browser awaiting an authorization code from LinkedIn that will be sent after the user has successfully logged in and granted the CRM Book permission.
@Subscribe
private void handleLinkedInAuthenticationEvent(ShowBrowserRequestEvent event) {
logger.info(LogsCenter.getEventHandlingLogMessage(event));
handleLinkedInAuthentication();
}
public void handleLinkedInAuthentication() {
try {
Oauth2Client.authenticateWithLinkedIn();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void authenticateWithLinkedIn() throws IOException {
...
String urlString = "https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id="
+ clientId + "&redirect_uri=" + redirectUri + "&state=...";
bWindow = new BrowserWindow(urlString);
bWindow.show();
}
Once this has happened, we fire a HideBrowserRequestEvent
in order to close the browser properly.
The Decrypter
will then be fired so that we do not store the app secret in plain text. This decision to use a Decrypter class is in the comments section of the Decrypter class, and duplicated here for convenience to the reader.
// Decrypts the s for use for OAuth
// While this is not an ideal situation, LinkedIn's OAuth API does not have a client-side authentication flow.
// This means that it will always require the app s for purposes of authentication.
// Because of this, building a native (desktop) app that authenticates with LinkedIn is not ideal.
// However, a number of sites have agreed that if you have to store the key in the code, then obscuring it to make
// it slightly more difficult for a potential hacker to get it is best. (They will need to run the app rather than
// just reading the plain text version)
// This is especially so because a LinkedIn S is not especially valuable, since anyone can create a LinkedIn app.
// Furthermore the chances of competitors abusing the secret to disable this application is minimal, since it is
// ultimately, a school project.
public void handleHideBrowser() {
Oauth2Client.closeBrowser();
Oauth2Client.getLinkedInS();
}
After the CRM Book has received the authorization code and the app secret from the decrypter, they are sent back to LinkedIn to request for an AccessToken. Once the AccessToken has been received, the user is considered to be successfully logged in, and the accessToken can be used by the CRM Book to make requests to LinkedIn on behalf of the user. The accessToken is also stored in the config.json file for future usage.
config.setAppSecret(accessToken);
ConfigUtil.saveConfig(config, config.DEFAULT_CONFIG_FILE);
The following sequence diagram shows how the linkedIn_login feature works. As seen, it is an events-driven design.

Design Considerations
Aspect: Storing the LinkedIn App Secret
-
Alternative 1 (current choice): Slightly encrypting the App Secret
-
Pros: Easy to implement, not trivial for attackers to get by scanning the source code.
-
Cons: App secret can be derived if the attacker runs the source code
-
-
Alternative 2: Store the App Secret in another server, and requesting it with an authentication code
-
Pros: Higher security, app secret not stored on GitHub
-
Cons: Significantly harder to implement. Requires user to have a username and password for the server not stored on GitHub.
-
-
Alternative 3: Store the App Secret in plain text
-
Pros: Much easier to code and implement
-
Cons: Bad security, secret will likely end up in a paste database somewhere in the internet
-
Share to LinkedIn Feature
Current Implementation
When the linkedin_share command is executed, it will call the Config class to get a current copy of the configurations used by the Application. It will also set the boolean value of postSuccess to false. This boolean value will be used to determine if we managed to successfully post to LinkedIn.
public static void postToLinkedIn() {
Config config = Config.setupConfig();
postSuccess = false;
Next, we create a JSON Object that LinkedIn requires, specifying the visibility (privacy) of the post, and the actual content of the post. By default, we set the visibility to be 'anyone', or in Facebook terms, 'Public'. An overview on LinkedIn’s share API can be found at https://developer.linkedin.com/docs/share-on-linkedin
With the JSON Object and the access_token taken from the user’s configuration file, we create a HttpPost Object and HttpClient Object, send it to LinkedIn, and analyze the response. In particular, if the response contains an updateUrl, it means that the post has been successfully posted, and we update the boolean value of postSuccess to true.
HttpPost httppost = getHttpPostObject(jsonToSend, accessToken);
HttpClient httpclient = getHttpClientObject();
JSONObject linkedInResponse = sendHttpRequestToLinkedIn(httppost, httpclient);
logger.info("LinkedIn Response is : " + linkedInResponse.toString());
if (linkedInResponse.has("updateUrl") || linkedInResponse.has("updateURL")) {
//if has updateURL then it successfully got posted
logger.info("Post has been successfully posted");
postSuccess = true;
}
Finally we use the postSuccess value to determine if we send a success or failed message to the user.
The following sequence diagram shows how the command works

Design Considerations
Aspect: Allowing salesperson to set privacy level of their post
-
Alternative 1 (current choice): Only option is 'anyone'
-
Pros: Salesperson always wants to share to as many people as possible. Reduces complexity of command and/or save on a command to set a default level of visibility.
-
Cons: Salesperson is limited to only posting public posts using CRM Book
-
-
Alternative 2: Allow salesperson to set a default visibility option using another command
-
Pros: Salesperson has greater flexibility in setting the visibility type of their post.
-
Cons: Have to write a new command that may not be used.
-
-
Alternative 3: Allow salesperson to specify visibility in share command
-
Pros: Salesperson has the greatest level flexibility in setting the visibility type of their post.
-
Cons: Increases complexity of the command
-
Saving a location for Google Maps
Current Implementation
To save a location in Google Maps, it does not matter whether CRM Book already has a pre-existing address. Rather, we always overwrite the config file to be updated with the latest address.
Config initializedConfig = Config.setupConfig();
initializedConfig.setUserLocation(address.toString());
The following sequence diagram shows how the command works

View directions to a Client’s office
Current Implementation
The current implementation of this feature is the same as when a person card is changed (Like the select command). The difference is that if the config file states that a user’s office location has been set, then load Google maps instead of doing a Google Search.
@Subscribe
private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) {
getConfig();
logger.info(LogsCenter.getEventHandlingLogMessage(event));
//if person has no home location set
if (config.getUserLocation() == null || config.getUserLocation().length() == 0) {
logger.info("No office location set, doing Google search");
loadPersonPage(event.getNewSelection().person);
} else {
String url = generateUrl(config.getUserLocation(), event.getNewSelection().person.getAddress().toString());
logger.info("Office location set, Load Google Maps. URL IS " + url);
loadPage(url);
}
}
The following sequence diagram shows how this feature works. The select command is used only if the user activated it via commandline. However, clicking on another person card without using the select command will work as well.
