Commit graph

1641 commits

Author SHA1 Message Date
William Carroll
b1c403f6b9 Create small command line program that parses arguments
Before starting my take-home assignment, the instructions advised me to create a
"Hello, world" program in the language of my choice. Since I'm choosing Haskell,
I created this example as my starter boilerplate.
2020-08-04 16:36:31 +01:00
William Carroll
ee8e75231c Set -Wall and fix warnings
I think setting -Wall is a sensible default and @dmjio confirmed this. After
putting this in my project's .ghci file, a few dozen warnings emerged. This
commit changes the code that causes the warnings.
2020-08-04 09:19:48 +01:00
William Carroll
9a19942c03 Add .ghci configuration file
Create a project-local .ghci file to define sensible
defaults (e.g. -Wincomplete-patterns).

TODO: Discover more GHC options to put in this file.

I would prefer to keep this at the project root, but because I'm running the
project from the src directory, I need to keep .ghci there.
2020-08-03 11:37:57 +01:00
William Carroll
b9ed4a2dc1 Partially support federated login
Two things:
1. I've never attempted to support this before.
2. It seems surprisingly and perhaps deceptively simpler than what I
   expected. I'm unsure what to do once Google's API authenticates the user. I
   currently look-up the user's role, trips, etc. using their email address. The
   role is stored in the Accounts table alongside username, email, password. I
   will speak with the interviewer tomorrow about this.
2020-08-02 21:27:08 +01:00
William Carroll
d6b91b93cb Allow managers to delete users
Borrow the allUsers component

TODO: Move many of these CRUD tables into Common and DRY-up usages across User,
Admin, Manager.
2020-08-02 21:02:22 +01:00
William Carroll
c2419cd912 Support updating trips from the client
Edit existing trips.
2020-08-02 20:56:29 +01:00
William Carroll
239ff24c95 Use ORDER BY to sort the response for GET /trips
SQL is quite useful.
2020-08-02 19:50:28 +01:00
William Carroll
0cb9642a8a Use valid dates for Trips.endDate in trips.csv
TL;DR: My trips.csv had invalid dates for the endDate column. "2020-15-30" is an
"invalid date" (according to FromField instance for Calendar.Day) bc 15 is not a
valid month (i.e. [1,12]).

@dmjio helped me take a look. When we poked around the SQL, we discovered:

```sql
SELECT endDate FROM TRIPS;       -- shows three records
SELECT date(endDate) FROM TRIPS; -- shows two records
```
2020-08-02 19:44:23 +01:00
William Carroll
1d5cf2e4b5 Support Admins inviting users from the client
The title says it all.
2020-08-02 18:02:15 +01:00
William Carroll
a3d783025a Add friendlier and more accurate instructions in invitation email
Copy Example:

To accept the invitation:
  POST /accept-invitation username=<username>
    password=<password> email=you@domain.tld
    secret=8c6b5719-7b1c-471c-bdea-7807b6c0866c
2020-08-02 18:00:29 +01:00
William Carroll
2632dc10fd Ensure /accept-invitation is POST and not GET
Debugged this bug!
2020-08-02 18:00:05 +01:00
William Carroll
90d1451895 Render "<count> days until" for upcoming trips
Per the assignment spec.
2020-08-02 17:45:34 +01:00
William Carroll
e9e84f6a08 Support POST /accept-invitation
Allow users to accept invitations that we email to them.

TL;DR:
- I learned how to write FromHttpApiData instances, which allows me to
  parse/validate data at the edges of my application; this substantially cleans
  up my Handler code.
2020-08-02 16:30:28 +01:00
William Carroll
25334080b9 Support POST /invite
Allow Admin accounts to invite users to the application.
2020-08-02 16:07:35 +01:00
William Carroll
fe609bbe58 Support CRUDing records on Admin page
TL;DR:
- Prefer the more precise verbiage, "Accounts", to "Users"
- Add username field to Trip instead of relying on session.username
- Ensure that decodeRole can JD.fail for invalid inputs
2020-08-02 15:15:01 +01:00
William Carroll
81c3db20d4 Allow managers to CRUD all account types (not just admins)
Per the assignment's instructions.
2020-08-02 14:31:00 +01:00
William Carroll
803db7a5b2 Support printing user's itinerary
- Define print.css with media=print type (note: could've been handled with
  @media queries)
- Define printPage port to interop with window.print() JS function
- Support UI.wrapNoPrint to wrap components with a the no-print CSS
2020-08-02 14:23:38 +01:00
William Carroll
699892883c Support deleting trips from the client
TL;DR:
- Ensure Types.TripPK in Types.hs uses Calendar.Day for startDate
- Prefer verbiage "GotCreateTrip" to "CreatedTrip"
- Extend Utils.deleteWithCredentials to accept a body parameter
- Support a delete button in the UI
2020-08-02 11:16:24 +01:00
William Carroll
d5bc6f963d Protect views from a State with an empty Session
This should simplify UserHome among other views.
2020-08-02 11:13:54 +01:00
William Carroll
57b6472e2f Define defaults for init in State.elm
Problem: When I'm working on a feature, I save my code, and elm-live reloads the
browser. This is usually good, except that the application state is
reinitialized, which usually means that the view changes.

I defined two state configurations, and I expect to define more:
- prod: The initial state for the application
- userHome: The state I'd like to use when developing a feature for the UserHome
  page.

Idea: For more ad-hoc configurations, I can store the application state in
LocalStorage and restore it in between page refreshes.
2020-08-02 10:51:26 +01:00
William Carroll
ac9629cad0 Assign fixed width to error banners
This makes the banners easier to center horizontally.
2020-08-02 10:33:42 +01:00
William Carroll
249e3113ff Support creating Trips from the frontend
*sigh* ... spent way too much time encoding/decoding date types...

I need my database, server, client, and JSON need to agree on types.

TL;DR:
- Add CSS for elm/datepicker library
- Create Common.allErrors to display UI errors
- Prefer Data.Time.Calendar.Day instead of newtype Date wrapper around Text
2020-08-01 23:04:06 +01:00
William Carroll
54eb29eae0 Prefer RecordWildCard syntax for toFields functions
Refactoring old code to conform to the latest fashion.
2020-08-01 12:29:31 +01:00
William Carroll
83f4f8e9d6 Prevent non-admins from creating Manager or Admin accounts
Client-side, I'm not exposing the role option to users. Server-side, I'm
asserting that requests to create Manager and Admin accounts are attempted by
users with a session tied to an admin account.
2020-08-01 11:48:55 +01:00
William Carroll
a3732300e1 Add exhaustive patterns to FromJSON Role instance
When someone enters something like role=mgr, return a helpful error message to
the user.

Note: I should enable the exhaustive patterns check for GHC.
2020-08-01 11:46:41 +01:00
William Carroll
9666d5dce1 Support sign-up
Toggle b/w logging in or signing up.

TL;DR:
- From my previous submission's feedback, disallow users from signing themselves
  up as admins, managers; I just removed the UI element altogether, even though
  the server still support this (TODO)
2020-07-31 19:33:18 +01:00
William Carroll
cf5d211477 Support UI.disabledButton
While this isn't necessary, it tidies up the code a bit.
2020-07-31 19:32:30 +01:00
William Carroll
4d30a80487 Support UI.textButton
Create a text-only button.
2020-07-31 19:32:14 +01:00
William Carroll
421c71c892 Support a basic client-side login flow
I will need to remove some of the baggage like:

- Scrub any copy about restaurants
- delete Restaurant.elm
- Change Owner.elm -> Manager.elm
2020-07-31 18:57:35 +01:00
William Carroll
29a00dc571 Configure non-simple CORS server-side
@dmjio says (probably correctly) that it's best to just serve the client from
the server and circumvent CORS issues altogether.

One day I will set that up. For now, this works... *sigh*
2020-07-31 18:31:52 +01:00
William Carroll
cdaa449670 Prefer PUT to PATCH
It was always a PUT. Nothing to see here, folks.
2020-07-31 18:30:03 +01:00
William Carroll
35b218c543 Return a JSON Session on a successful POST /login
This will make the UX on a the client-side smoother.
2020-07-31 18:30:03 +01:00
William Carroll
c8ed6e51fe Read CLIENT and SERVER endpoints from .envrc
In the spirit of DRY.
2020-07-31 18:28:15 +01:00
William Carroll
1d7c77f51d Support POST /unfreeze
Allow admins and managers to unfreeze accounts that we froze for security
reasons.
2020-07-31 11:37:45 +01:00
William Carroll
43eff5f1d0 Prefer RecordWildCards for FromJSON instances
Stylistically, I think this looks cleaner.
2020-07-31 11:27:47 +01:00
William Carroll
ed557fb6be Support PATCH /trips
Support a top-level PATCH request to trips that permits any admin to update any
trip, and any user to update any of their trips.

I'm using Aeson's (:?) combinator to support missing fields from the incoming
JSON requests, and then M.fromMaybe to apply these values to any record that
matches the primary key.

See the TODOs that I introduced for some shortcomings.
2020-07-31 11:25:36 +01:00
William Carroll
7d64011cbd Protect GET /trips with a session cookie
When an admin requests /trips, they see all of the trips in the Trips
table. When a user requests /trips, they see only their trips.
2020-07-31 10:55:10 +01:00
William Carroll
75437b01b6 Check for GTE instead of GT
Somebody incremenet the total number of off-by-one errors that I've made in my
career. I think the current count is 99... or is it 100? 101? Who knows?!
2020-07-30 19:53:46 +01:00
William Carroll
ea31a01497 Debug LoginAttempts.increment
When this was an UPDATE statement with a WHERE clause, and the LoginAttempts
table was vacant, nothing would happen. Thankfully, SQLite supports an UPSERT
clause so that I can INSERT a new record or UPDATE conditionally.

And the best part is: it works!
2020-07-30 19:52:37 +01:00
William Carroll
8ebc89b44b Remove erroneous parens around columns in SELECT statement
These were causing runtime errors... whoops!
2020-07-30 19:52:04 +01:00
William Carroll
6ecab8c3a6 Prefer SELECT (a,b,c) to SELECT *
"SELECT *" in SQL may not guarantee the order in which a record's columns are
returned. For example, in my FromRow instances for Account, I make successive call

The following scenario silently and erroneously assigns:

firstName, lastName = lastName, firstName

```sql
CREATE TABLE People (
  firstName TEXT NOT NULL,
  lastName TEXT NOT NULL,
  age INTEGER NOT NULL,
  PRIMARY KEY (firstName, lastName)
)
```

```haskell
data Person = Person { firstName :: String, lastName :: String, age :: Integer }

fromRow = do
  firstName <- field
  lastName  <- field
  age       <- field
  pure Person{..}

getPeople :: Connection -> IO [Person]
getPeople conn = query conn "SELECT * FROM People"
```

This silently fails because both firstName and lastName are Strings, and so the
FromRow Person instance type-checks, but you should expect to receive a list of
names like "Wallace William" instead of "William Wallace".

The following won't break the type-checker, but will result in a runtime parsing
error:

```haskell
-- all code from the previous example remains the same except for:

fromRow = do
  age       <- field
  firstName <- field
  lastName  <- field
```

The "SELECT *" will return records like (firstName,lastName,age), but the
FromRow instance for Person will attempt to parse firstName as
Integer.

So... what have we learned? Prefer "SELECT (firstName,lastName,age)" instead of
"SELECT *".
2020-07-30 18:52:45 +01:00
William Carroll
dec8890190 Verify users' email addresses when they attempt to sign-up
Lots of changes here:
- Add the GET /verify endpoint
- Email users a secret using MailGun
- Create a PendingAccounts table and record type
- Prefer do-notation for FromRow instances (and in general) instead of the <*>
  or a liftA2 style. Using instances using `<*>` makes the instances depend on
  the order in which the record's fields were defined. When combined with a
  "SELECT *", which returns the columns in whichever order the schema defines
  them (or depending on the DB implementation), produces runtime parse errors
  at best and silent errors at worst.
- Delete bill from accounts.csv to free up the wpcarro@gmail.com when testing
  the /verify route.
2020-07-30 18:38:46 +01:00
William Carroll
30838b8df7 Add Haskell client library for MailGun
Whichever package is on nixpkgs right now is broken, so I'm using `fetchGit` and
`callCabal2nix`.

Create Email module exposing a simplifies `send` function that partially applies
some of the configuration options.
2020-07-30 17:07:49 +01:00
William Carroll
b6e8389edd Read env variables using envy library
Using my dear friend's, dmjio's, excellent library, envy -- to read and parse
variables from the system environment.

I added and git-ignored the .envrc file that contains API secrets. I'm using
Envy to read these values, so that I don't hard-code these values into the
source code.
2020-07-30 13:58:50 +01:00
William Carroll
385164c6af Authorize endpoints
If I ever fully learn `servant-auth`, I'll probably recognize how naive this
hand-rolled solution is. But it works! And the code is pretty declarative, which
I like.
2020-07-30 10:23:55 +01:00
William Carroll
ca26fcd523 Debug erroneous table name
"Session" doesn't exist, but "Sessions" does.
2020-07-30 09:51:32 +01:00
William Carroll
ef40622a87 Mark Dangal as watched
Many Bollywood movies have excellent acting, excellent directing, excellent
storytelling, but in my opinion, they spoil this with unnecessary musicals
interspersed throughout the films.

Dangal is a notable exception here. Overall, I'd say that this movie is
appropriately rated!
2020-07-30 09:35:01 +01:00
William Carroll
1e82ea4b26 Mark Hitchcock's Vertigo as watched
Watched the famous "Vertigo" with the timeless Jimmy Stewart. Overall I'd say
that the film is overhyped, but worth watching nevertheless.
2020-07-30 09:33:52 +01:00
William Carroll
fdd51f626c Fully support login, logout
Refactor my handlers to use the `Handler a` type instead of `IO a`; this allows
me to throwError inside of handlers that Servant properly handles. Previously I
was creating 500 errors unnecessarily.
2020-07-29 20:26:23 +01:00
William Carroll
ab12be7840 Support looking up a session by its UUID
We need to read a session from the session table using its UUID.
2020-07-29 20:21:56 +01:00