Commit graph

43 commits

Author SHA1 Message Date
William Carroll
17c68d654b Prefer reading secrets.json to using pass show
I'm attempting to maintain a top-level secrets.json that defines all of the
sensitive data that I'd like to version-control without exposing everything in
cleartext to the world. To that end, I'm using `git secret`, which will use
`gpg` to encrypt secrets.json everytime I call `git secret hide` and decrypt
everytime I call `git secret reveal`.

I'm going to try this until I don't like it anymore... if that day comes...

I should write a blog post about my setup to solicit useful feedback and share
my ideas with others.
2020-08-20 18:31:37 +01:00
William Carroll
59f7481411 Revise previous opinions about absolute paths GT <bracket-notation>
Unforeseen problem: `buildkite-agent` runs its builds in a separate directory,
so if I want the `nix-build` command to build the newly checked out code, I need
to set <briefcase> to the CWD.
2020-08-20 11:26:31 +01:00
William Carroll
81aa32fe71 Support POST /create-payment-intent
Interact with Stripe's payment_intents API endpoint.

I'm not committing the index.html that contains client-side code that interacts
with the /create-payment-intent endpoint, but it contains sensitive information,
so I'm omitting it for now.

TL;DR:
- Define POST /create-payment-intent endpoint
- Include envStripeAPIKey in Context record
- Define a top-level Stripe module for making API calls
- Define types and instances that align with Stripes request and response types
- Depend on the Req library: a higher-level library than http-client
2020-08-20 11:26:30 +01:00
William Carroll
2da4b12266 Consume buildHaskell functions
Use the newly defined `buildHaskell` function for a few of my existing Haskell
projects. So far, it works as intended!
2020-08-12 16:28:39 +01:00
William Carroll
4ff1ea291c Drop support for ServantT transformer type for server
After burning a few hours wrestling with the type system, I decided to revert to
the simpler `Server API` type instead of the `ServantT` transformer type.

The problem is that I couldn't write a MonadError instance for `RIO Context`,
which is my `AppM` (i.e. application monad). Using `throwIO` in the server
handlers results in 500 errors, which is not what I wanted. I'm still pretty
fuzzy about what's happening; I now know that exception handling in Haskell is
pretty gnaryly. I may revisit this at a later time when my knowledge is more
extensive. For now: time to fry bigger fish.

An easier abstract is for me to pass `T.Context` into `server` as an argument,
which after all is what a Reader does.

TL;DR:
- Read server, client ports from .envrc
- Define a top-level Failure type (empty for now)
- Define a top-level Success type
- Define App as RIO Context (Either Failure Success)
2020-08-10 15:02:05 +01:00
William Carroll
f61ed25755 Prefer ServantT for server to consume App context
Long story -> short: I'd like to access my App monad from within my Servant
handlers.

While this code type-checks, I'm not sure it's working as intended. Needing to
change throwError to throwIO fails the "smell test". I expect to refactor this
code, but I'm calling it a night for now.
2020-08-09 23:15:12 +01:00
William Carroll
bbcd0bf27d Replace Prelude with RIO
I believe RIO stands for: "ReaderT <something-something> IO", which is a nod to
the top-level application data type:

```haskell
-- This is a simplification
newtype RIO env a = RIO { runRIO :: ReaderT env a () }
```

I read about RIO from an FP-Complete blog post a few months ago, and now I'm
excited to try it out for a real project. Bon voyage!
2020-08-09 22:17:19 +01:00
William Carroll
7d85ba559d Move Haskell-related shell.nix code into its own shell.nix
I'm getting tired of:

```shell
$ cd <project-root>
$ nix-shell
$ cd src/server
$ ghci Main.hs
```

Instead:

```shell
$ cd <project-root>/src/server
$ ghci Main.hs
```
2020-08-09 22:11:39 +01:00
William Carroll
26b7237aab Sketch database schema
Defining a few tables in init.sql to sketch a few records that I need to
persist.
2020-08-09 10:22:12 +01:00
William Carroll
9df2b49afd Initialize a default.nix for nix-build
As the previous commit mentions, I'm attempting to build and deploy this project
with `nix-shell` and `nix-build` instead of `cabal` and `stack`.

I'm in the Hamburg airport right now, and my internet connection isn't stable
enough to test this, so I'm committing it until I can more robustly test it.
2020-08-09 10:19:36 +01:00
William Carroll
119c8e9df9 Add common language extensions to .ghci
I'd like to see if I can avoid using `cabal` and `stack` and build and deploy
this application using `nix-shell` and `nix-build` only. Let's see how that
goes.
2020-08-09 10:18:46 +01:00
William Carroll
e8f35f0d10 Consume GoogleSignIn.validateJWT
TL;DR:
- Consume GoogleSignIn.validateJWT in the Handler for /verify
- Rename validation fn to validateJWT
- Prefer Text to String type
2020-08-08 17:57:11 +01:00
William Carroll
8a7a3b29a9 Add tests for "exp" field of the JWT
Assert that the exp field of the JWT is "fresh".
2020-08-08 14:47:39 +01:00
William Carroll
f1883b2790 Test that the JWT's iss field meets our expectations
The JWT should match "accounts.google.com" or "https://accounts.google.com". If
it doesn't, we produce a validation error.

TL;DR:
- Group all failed stringOrURI function calls as StringOrURIParseFailure errors
2020-08-08 14:08:11 +01:00
William Carroll
526728eb89 Test that an improperly encoded JWT returns a DecodeError
The subject of this commit message says it all.
2020-08-08 13:46:57 +01:00
William Carroll
d34b146702 Tests valid and invalid JWTs for the "aud" field
Test that when the JWT contains the client ID for my Google app, the JWT is
valid, and when it doesn't, it's invalid.
2020-08-08 13:44:22 +01:00
William Carroll
926d8e643e Update jwtIsValid API to return IO Bool
I need IO for:
- Getting the current time to validate `exp`
- Making an HTTP request to Google's token verifier endpoint
2020-08-08 11:18:49 +01:00
William Carroll
3eaf6e5aea Remove redundant deps from API.hs
Thank you, -Wall. You are truly an unsung hero.
2020-08-08 11:10:28 +01:00
William Carroll
7b8ec4170a Begin work for supporting GoogleSignIn server-side
I'm attempting to be an obedient boy and implement this and future features
using TDD.

TL;DR:
- Defined a few tests
- Defined an empty GoogleSignIn module
- Defined a Fixtures module to quickly create JWTs to test
2020-08-08 11:10:19 +01:00
William Carroll
9dcbd0d067 Define Utils module
Dumping grounds for personal, stylistic functions intended to improve readabily
and writability (in that order).
2020-08-08 11:06:53 +01:00
William Carroll
a7ddb56b9b Support echo server to test POST /verify
TL;DR:
- Add common dependencies like Servant, Aeson, Warp, Cors
- Define a POST /verify endpoint for our client to hit
- POST to /verify client-side onSignIn
2020-08-06 22:23:06 +01:00
William Carroll
1fc1087014 Support Google Sign-in client-side
TODO: Support Google Sign-in server-side

Also:
- Add Haskell to project's shell.nix
- Add stubbed Main.hs and Spec.hs
- Add common .ghci file
2020-08-06 21:54:25 +01:00
William Carroll
7a7c29d46c Restore default view to Overview
I often debug by changing the values of State.Model in the State.init
function. I usually revert these these chage; this time I didn't.
2020-04-19 19:49:15 +01:00
William Carroll
d84d0000e0 Correct all G_sharp pitch classes
I incorrectly modelled all of the G-sharps in my application as belonging to the
G pitchClass, which resulted in a strange bug where vieChord printed "G minor
Root position", but the Piano highlit a G minor.

I checked the other accidentals, and it looks like everything is properly
classified.
2020-04-19 19:11:17 +01:00
William Carroll
ae31090a46 Remove unused Msg's
I'm sure this app contains more unused code. I would like to find some Elm tools
for detecting and deleting dead code, but this isn't my current priority.

My current priority is dogfooding this app until I find it genuinely useful for
myself.
2020-04-19 19:02:25 +01:00
William Carroll
541c40cd2f Ensure only whitelisted chord inversions present in initial state
Whoops...
2020-04-19 19:02:17 +01:00
William Carroll
a059c32403 Display "Get ready..." message before practicing
This is a temporary solution. Ideally I would like to handle this with the
following:

- Show the flashcard for a chord shortly after beginning a practice session
- Display a small 3...2...1... countdown timer immediately after beginning a
  practice session

I need to dig more deeply into Elm's Time module and subscriptions to better
understand how to properly solve this problem. In the meantime, please tolerate
this short-term solution.
2020-04-19 18:51:42 +01:00
William Carroll
4a8f750ba8 Restore support for whitelisted chord inversions
Allow users to include or exclude chord inversions.
2020-04-19 18:36:22 +01:00
William Carroll
14f11823ff Drop support for PracticeMode
For now, I'd like to support selecting keys and whitelisting inversions.
2020-04-19 18:23:01 +01:00
William Carroll
d134db700f Support a FlashCard before showing the notes that comprise a chord
My much anticipated feature: first prompt the user for a name of a chord, then
show the user that chord.

Cascading changes:
I changed the "Tap to practice" overlayButton's opacity from 30% to 100% because
pausing when showFlashCard is True causes the two piece

TIL:
You can batch Elm Subscriptions using the Sub.batch function.

What I haven't learned yet:
How to best handle rotating screens for mobile devices (i.e. portrait
vs. landscape modes). In time...

What's left?
- Support sound
- Support a fine-tune section of the preferences
- Support tablet and web browser variants
- Ask users for the "I chord" instead of asking "C major Root position"
- More styling (of course)
2020-04-19 15:32:20 +01:00
William Carroll
f92fe97aff Create Tailwind module
Moving the UI.tw function into Tailwind.use. Creating and consuming some
functions like Tailwind.if_ and Tailwind.when to make it easier to conditionally
style some of my components.
2020-04-19 13:42:37 +01:00
William Carroll
7b2163d804 Ensure the overlayButton is truly h-screen and w-screen
Now the "Tap to practice" button fully covers the screen.

- Dropped support for a Piano direction (for now)
- Using w-full and w-1/2 for piano key "length"
2020-04-19 13:14:18 +01:00
William Carroll
2fe6d7a10c Responsively size UI
TL;DR: scale down UI for non-mobile devices.

I pulled the screen resolution for my phone, the Google Pixel 4, off of the
internet. I created a device profile in Chrome to develop this application
specifically for my phone. To my surprise, when I opened the app on my phone,
many of elements that looked good in Google Chrome, looked askew on my phone. I
needed to troubleshoot.

Here's how I did that:
I used Tailwind to responsively color the bg for each breakpoint to see if my
device was sm, md, lg, xl (according to Tailwind's breakpoint
terminology). After reading Tailwind's documentation and comparing their
breakpoints with my Pixel 4's width (i.e. 1080px), I figured that my device
would be lg. It's not; it's md, which I confirmed by using ngrok to load
localhost:8000 on my phone and see that the background-color was
"md:bg-green-600".

I'm still unsure why my device is not lg, but knowing that my device was md
was enough to fix many of the styling issues. My current theory is that while
my screen's resolution is 1080 wide, the pixel density affects the media query
for the breakpoint.
2020-04-19 13:05:55 +01:00
William Carroll
74ebb8e869 Set the selectedChord to Nothing when setting a key
This helps us avoid showing a chord from a key that the user did not whitelist.
2020-04-19 00:31:42 +01:00
William Carroll
8620d86fd0 Prune {Select,Deselect}AllKeys actions
Removing more unused code attempting to focus this app's scope.
2020-04-19 00:31:06 +01:00
William Carroll
11b140b6ae Highlight root note of each chord
Refactor the Piano component to highlight the root note of each chord. If this
makes things too easy, I can support this as a preference.

Also:
- Reduced the number of keys that the piano displays and increased the key
  thickness to reclaim the space
- Preferred using Tailwind selectors instead of inline styling where applicable
- Call List.reverse on the keys to ensure that the top-most note is a lower note
  than the bottom-most note

TODO:
- Support showing only the name of the chord and not just the notes that
  comprise that chord
- Rewrite the function that generates the chords for a given range of notes
- Consider supporting a dark mode
2020-04-19 00:21:37 +01:00
William Carroll
c6132ab1d6 Remove horizontal padding
Google Chrome's device preview doesn't resemble what I see when I use my phone
to visit this page.
2020-04-18 20:23:02 +01:00
William Carroll
9e855f7427 Create Overview for Learn Piano Chords
Since I've published this, I should include an Overview page to orient potential
users. This Overview could be better -- as could many things with this app --
but it's a start, and I'm seeking small wins.
2020-04-18 19:51:57 +01:00
William Carroll
6dc48753f5 Prefer "Tap" to "Press"
I'm preferring the verb "tap" to "press".
2020-04-18 19:49:25 +01:00
William Carroll
82ebc0ad19 Debug unresponsive button press for selectKey
Observed problem: Tapping "C major, A minor" key, which LPC sets by default,
does not unset it.

Bug: handleClick passed the relativeMinor Key but the default value in
State.Model is the C Major key. We would toggled b/w [Cmajor] ->
[Cmajor,Aminor], and because toggled checked if either Cmajor or Aminor was
present, it was always true.

Solution: Check relativeMajor to set toggled.
2020-04-18 18:51:23 +01:00
William Carroll
441fe3e32e Tidy app
Now that I have a deployed an MVP of my app, I am tidying things up to support
the next phase of development.

TL;DR:
- Moved application Model-related code into State module
- Moved each View into its own module
- Deleted unused ChordInspector component
- Deleted unused Msg's, {Increase,Decrease}Tempo
- Deleted misc unused code
2020-04-18 14:58:16 +01:00
William Carroll
720d727d64 Orient "Press to practice" button
Rotate the "Press to practice" copy to ensure that it is readable in landscape
mode.
2020-04-18 14:24:41 +01:00
William Carroll
f0803547e4 "Chord Drill Sergeant" -> "Learn Piano Chords"
In the spirit of "keep it simple, stupid", I am naming this application as
closely to the functionality as I can imagine.
2020-04-18 13:30:38 +01:00