TinyDevCRM Update #4: Token-based authentication + hourly journal

Table of Contents

This is a summary of TinyDevCRM development for the week of February 29th, 2020 to March 7th, 2020.

Goals from last week

  • [❓] Ship some form of API backend to https://api.tinydevcrm.com
  • [❓] Wire up token-based authentication workflow to React.js front-end

What I got done this week

  • [❌] Ship some form of API backend to https://api.tinydevcrm.com
  • [✔] Wire up token-based authentication workflow to React.js front-end


  • Weeks to launch (primary KPI): 2 (has been at 2 for the past 4 weeks or so)
  • Users talked to: 0

Hour-by-hour Journal

March 3rd, 2020 (START)

  • 08:00AM-09:00AM: N/A

  • 09:00AM-10:00AM: N/A

  • 10:00AM-11:00AM: N/A

  • 11:00AM-12:00PM: N/A

  • 12:00AM-01:00PM: Watched some videos on YC Startup School to catch up on my backlog. Specifically, Andora Cheung's video on time management. Some of the specific tips she mentioned include:

    • Keep an hourly journal of your day, and evaluate progress from the very beginning

    • Internalizing focus on talking to users or shipping product and not BS

    • Keep an impact / complexity matrix as the context for your todo list, and evaluate it against your chosen KPIs

    • The importance of keeping both a substantive maker's schedule and manager's schedule through deep work

    I haven't been doing these very well. I've been treating this as a side project with full-time hours, and as Adora and a friend mentioned, you're not just burning hours, you're burning the opportunity cost of not working on your startup. Hopefully, this public log will make that better.

  • 01:00PM-02:00PM: Answered Pioneer.app feedback from Monday. Learned about UIPath and the concept of robotic process automation, which hits the nail on the head in terms of value proposition and what I want to build. tinydevrpa instead of tinydevcrm.

  • 02:00PM-03:00PM: Reading through AWS Elastic Beanstalk documentation. Stuck processing complexity of putting together a heterogeneous framework using Flask, and comtemplating throwing it all out in favor of learning Django and having well-vetted dependencies. Decided to get rid of authentication for the first deployment, since it's complicated enough to cause this kind of confusion.

  • 03:00PM-04:00PM: Took a 30 minute walk to clear my head, took another look at JWT authentication to see whether I should switch from Flask to Django, JWT tutorials galore for Django and React, including this one posted on February 21st of this year. `django-rest-framework-simplejwt also supports Django 3.x.

    I hate to say it. But I think I might have to learn Django. If there's a tutorial available already, I think it should be less pain, and Django's ecosystem is much better vetted, but it also means I lost some time just picking between frameworks. I don't like it, but I think going with Django now will pay off in the long run (including other projects with backends).

    I'll see how much of this tutorial I can tie into my existing MVP, and not have to re-create an entirely separate repository as I did with the Flask JWT tutorial.

  • 04:00PM-05:00PM: Worked on Django JWT tutorial, within the context of the TinyDevCRM API. Got to Section 1-2a. Configuring DRF + DRF Simple JWT.

  • 05:00PM-06:00PM: Worked on Django JWT tutorial, within the context of the TinyDevCRM API. Got to Section 1-2e. Registering a user after encountering some minor blockers. Pausing some work in order to prepare for a commitment at 7:30PM.

  • 06:00PM-07:00PM: Took the YC Startup School TripleByte quiz for full-stack technical founders (20-25 minutes), did some prep for the commitment but required a video which lagged due to existing memory and network constraints, continued work on the React.js + Django JWT tutorial.

  • 07:00PM-08:00PM: N/A

  • 08:00PM-09:00PM: N/A

  • 09:00PM-10:00PM: N/A

  • 10:00PM-11:00PM: N/A

  • 11:00PM-12:00PM: N/A

March 4th, 2020

  • 08:00AM-09:00AM: N/A

  • 09:00AM-10:00AM: N/A

  • 10:00AM-11:00AM: N/A

  • 11:00AM-12:00PM: N/A

  • 12:00AM-01:00PM: Checked Pioneer.app and YC Startup School for updates, starting on Section 1-2e. Registering a user.

  • 01:00PM-02:00PM: Finished Section 1-2e. Registering a user, and finished Section 1-2f. Creating and testing a protected view. Added one flight rule around Django debugging for documentation purposes. Preparing for a commitment at 2:00PM.

    I consistently encountered a problem with Python's tuple type, where (<str>) will resolve to str, while (<str>,) will resolve to a tuple. This resulted in a number of errors, such as one similar to this Stack Overflow question, where Django will spit out some form of unparseable stack trace.

    This problem was exacerbated by lack of immediate debuggability. Adding import ipdb; ipdb.set_trace() resulted in no debug shell available. I think this may be a problem with not running the Django process with pdb context. It does seem possible, as shown by this blog post, where you use python -m pdb manage.py runserver instead of python manage.py runserver.

    Section 2 of the tutorial may not be directly applicable, since the tutorial's version of React.js is coupled to Django's index.html front-end view and a custom toolchain, vs. a separately deployed application and the create-react-app toolchain.

  • 02:00PM-03:00PM: Commitment at 2:00PM-2:30PM, perused front-end documentation for about another ten minutes, Started templating login form and signup form with state variables, as part of Section 2-2) Preparing React / Forms.

  • 03:00PM-04:00PM: ?? (Not sure where this hour went). Pretty bad.

  • 04:00PM-05:00PM: Relocating work environments from one library to another to go walk and take a mental break.

  • 05:00PM-06:00PM: Making XMLHttpRequest to http://localhost:5000, and got an error due to Cross-Origin Site Request (CORS) not loading ‘Allow-Access-Cross-Origin’ HTTP header on response. Using django-cors-headers in order to address this issue. Got login flow working, where I entered an email as a username (due to following the tutorial, I didn't realize that my desired workflow differed in this regard), and a password, and got a JWT refresh and access token back as a result. Working on JWT configuration for protected resources. Updated React component DashboardBase to inherit this.props.children to insert a dummy component. Added a dummy component to test resource protection via JWT, verified it works (though extremely naively, no access token auto-refresh based on refresh token liveness).

  • 06:00PM-07:00PM: Finished and verified that auto-refresh for protected resources via Axios interceptor works properly if a refresh token exists in localStorage. Got naive signup forms to work, though if access/refresh tokens do not work, backend 401s. Fixed it by adding empty authentication_classes to backend views so that django-rest-framework can create a public view. Beginning work on Section 2-4) Logging out and blacklisting tokens.

  • 07:00PM-08:00PM: N/A

  • 08:00PM-09:00PM: N/A

  • 09:00PM-10:00PM: N/A

  • 10:00PM-11:00PM: N/A

  • 11:00PM-12:00PM: N/A

March 5th, 2020

  • 08:00AM-09:00AM: N/A

  • 09:00AM-10:00AM: N/A

  • 10:00AM-11:00AM: Commitment from 10:00AM - 12:00PM

  • 11:00AM-12:00PM: Commitment from 10:00AM - 12:00PM

  • 12:00AM-01:00PM: Moved over to the library, getting ready for Pioneer.app call as well as AWSome Day Online by AWS, checked out the AWS webinar and decided to download resources after the fact due to pacing issues. Found out more about various AWS events and AWS certifications which on second thought do seem to be useful. Handling some minor email things. Working on Section 2-4) Logging out and blacklisting tokens for JWT tutorial.

    I don't see the ability to create migrations because of a change in the simplejwt settings for some reason. Not sure whether this is database-specific, but I'm also using sqlite3 (same as the tutorial).

  • 01:00PM-02:00PM: Continuing to work on Section 2-4) Logging out and blacklisting tokens for JWT tutorial. Implemented logic and got HTTP 400 Bad Request from API. Inspected the source code for how RefreshToken was implemented, and I forgot to add rest_framework_simplejwt.blacklist_token to INSTALLED_APPS. Added, then ran migration. Successfully tested JWT authentication and finished tutorial.

    Hopped onto weekly Pioneer.app video conference in order to provide biweekly Pioneer.app update.

  • 02:00PM-03:00PM: Finished up Pioneer.app call, relocating from library to home in order to take a midday break and eat lunch.

  • 03:00PM-04:00PM: Ate lunch, went back to work. Updated and added Basecamp tickets for TinyDevCRM API, based on findings from JWT authentication tutorial.

  • 04:00PM-05:00PM: Began working on re-creating JWT tutorial logic but for the endpoints I actually want. Hopefully this isn't duplicating too much work, I just want (and could really use) the practice of setting up my own Django API endpoints and take those brave baby steps.

  • 05:00PM-06:00PM: Working on versioning my API, and realized that Django does not support nested apps (at least the kind I was thinking about). So I ended up creating a bunch of files that Django settings will not recognize, because namespacing within settings isn't supported by Django. After looking at the documentation, REST API versioning appears to be a Django REST Framework feature, with associated documentation.

    This was pretty painful to realize, since it meant I should go back and refactor some of the work I had already done. I'm not sure why I thought it was a good idea to just jump in without reading the documentation, but I'm glad the documentation exists and I didn't waste too much time figuring this part out, and that I already have something working from the tutorial. Going to relocate environments to try and remain productive.

  • 06:00PM-07:00PM: Relocated environment, finished namespacing /v1/auth service and established working API versioning behavior for Django REST Framework going forward.

  • 07:00PM-08:00PM: Successfully created API endpoint /v1/auth/users/create, reworking the CustomUser model to have minimal fields and inherit from AbstractBaseUser instead of AbstractUser. Django admin dashboard broke because some assumptions Django's UserManager model makes aren't satisfied by CustomUser, specifically that username is not the unique constraint, but the primary email is. Now the Django admin dashboard is authenticating correctly, but redirecting and not making the data visible.

    I'm honestly not sure whether a “primary email” as a unique constraint is a good idea, but I personally don't change email addresses that much, I'd prefer to have full names listed on the dashboard (since this tool isn't really meant to have users engage with other users), and I don't like having extraneous or unnecessary data stored.

  • 08:00PM-09:00PM: YC startup school weekly video-based group session.

  • 09:00PM-10:00PM: Relocating due to library closing at 9. Located a tutorial on how to implement AbstractBaseUser and BaseUserManager properly, here. N/A otherwise.

  • 10:00PM-11:00PM: N/A

  • 11:00PM-12:00PM: N/A

March 6th, 2020

  • 08:00AM-09:00AM: Morning routine. I think this hour I managed my time decently well here.

  • 09:00AM-10:00AM: Morning routine. *Spent an hour relaxing and eating breakfast, this is one hour I could have optimized better.

  • 10:00AM-11:00AM: Updated habit tracker with additional habits, updating financial ledger, updated habits and todo list, prep for commitment at 11AM

  • 11:00AM-12:00PM: Commitment at 11AM for the hour.

  • 12:00AM-01:00PM: Commitment from 11AM stretching to 12:30PM, ate lunch, relocated environments.

  • 01:00PM-02:00PM: Not sure where most of this hour went. Checked Pioneer.app and YC Startup School. Apparently the deal for AWS sweetened significantly, which was a pleasant surprise for me. I'd like to say thanks to AWS and YC for re-negotiating this deal in favor of SUS participants.

  • 02:00PM-03:00PM: Resolved broken CustomUser model and CustomUserManager manager to my satisfaction. Keeping number of fields as small as possible, and using only primary_email as the required field and making all other fields inherited or optional, to make migrations easier (right now I am deleting all migration files and db.sqlite3, which I cannot do after MVP launch).

  • 03:00PM-04:00PM: Working on proper /v1/auth/tokens/obtain/ API endpoint, and finished that endpoint. Working on proper /v1/auth/tokens/refresh/ API endpoint, and finished that endpoint. Working on proper /v1/auth/tokens/blacklist/ API endpoint, and finished that endpoint. Backend API authentication service should be completed. Switching to front-end development.

  • 04:00PM-05:00PM: Updated login form to use updated V1 APIs, and confirmed correct response with JWT refresh and access tokens. Updated signup form to use updated V1 APIs, and confirmed correct response with 201 response with full name and primary email. Added client-side validation logic around password confirmation.

  • 05:00PM-06:00PM: Relocated environments, removed client-side validation logic for the time being, added tasks for front-end using Basecamp tickets and Tracked (I should probably buy some Post-It notes at this stage, and start looking at the complete user workflows possible through the entire MVP since I can't fit things into my head right now), creating a new protected backend resource for managing concrete data.

  • 06:00PM-07:00PM: Testing new Django app concrete_data for managing concrete data to check that API endpoints are registered and still protected via token-based authentication by creating a test endpoint. Endpoints are protected. Finding myself running out of steam, working on adding former Bytes by Ying objectives to Basecamp ticketing system.

  • 07:00PM-08:00PM: N/A

  • 08:00PM-09:00PM: N/A

  • 09:00PM-10:00PM: N/A

  • 10:00PM-11:00PM: N/A

  • 11:00PM-12:00PM: N/A

March 7th, 2020

  • 08:00AM-09:00AM: N/A

  • 09:00AM-10:00AM: N/A

  • 10:00AM-11:00AM: N/A

  • 11:00AM-12:00PM: N/A

  • 12:00AM-01:00PM: N/A

  • 01:00PM-02:00PM: N/A

  • 02:00PM-03:00PM: N/A

  • 03:00PM-04:00PM: N/A

  • 04:00PM-05:00PM: N/A

  • 05:00PM-06:00PM: N/A

  • 06:00PM-07:00PM: N/A

  • 07:00PM-08:00PM: N/A

  • 08:00PM-09:00PM: N/A

  • 09:00PM-10:00PM: N/A

  • 10:00PM-11:00PM: N/A

  • 11:00PM-12:00PM: N/A

RescueTime statistics:

Week of March 1st to March 7th (inclusive): 51h 31m (direct screen time, no explicitly marked offline time), 14h 57m software development (much of this is overinflated since I do many things in VS Code include writing reports)

Goals for next week

  • Ship some form of backend to AWS Elastic Beanstalk to get a full-stack workflow going in production
  • Figure out how to use PostgreSQL instead of SQLite for Django while self-deploying instance (i.e. don't use RDS)
  • Talk to at least 1 person on my early adopters list in order to build accountability

Things I've learned this week

  • The Django ecosystem isn't that scary after all. I never dipped my toes into the Django ecosystem because I felt for smaller projects, Flask was more appropriate and quicker to get started. On the contrary, Django has more (quality) tutorials, better accepted culture and conventions, more extensions and maintainers, all of which creates a better environment to write code in.

  • Configuration is hard. Every project necessarily has to ship with its own configuration settings, and given how configurations evolve, there's not a great way to manage them besides strings and structs in files. Django has ‘settings.py’, where many of the settings just look arbitrary. On the positive side, having a more data-driven method of configuration management makes it easier to develop more complex extensions (even django-rest-framework-simplejwt had a good number of knobs to turn, which goes to show how far out of my depth I am), and it's easier to understand the behaviors of middleware (such as authentication pipelines). I used to hate middleware, because I never understood how it worked, and I generally resent “magic” in my code. Exposing it as a series of lists, that Django tries sequentially, helps me understand how Django might fail over.

    I do appreciate SQLite after this week. I think having just a file that you carry around in a single instance makes that first deployment step much easier, especially since there's so much more configuration overhead for other parts of the stack (like even just setting up CI/CD on the backend using Jenkins / AWS CodePipeline).

  • At this time, I remain unconvinced about the need to implement front-end validation. It's not that it's not important. For larger systems, it could help in cutting down on the number of unnecessary requests, and it could save the user some time by avoiding round-trip requests to the server. For me though, I want my clients to be cheap, and I'm concerned about duplicating validation logic and having to maintain two sets of validation For example, if you update a password rule in the database, that rule would mutate validation logic on not just the backend(s) but the frontend(s) as well. You could write a validation module in JavaScript, but I'm concerned that would imply usage of Node.js or something JavaScript on the backend, and I'm not willing to pay that kind of price for validation. In the future, I might write a separate validation module in backend language of choice, and transpile to WebAssembly and load that validation as a JavaScript blob.

  • REST API endpoint conventions are hard. “There are two hard things in computer science: cache invalidation and naming things”. I find that holds true for API naming. I get that this is still just an MVP, but I don't want to form a habit of breaking backwards compatibility, or having to iterate on the number of API versions I need to support. For me, this results in a very “sparse” API. I'd rather issue an HTTP POST request /users/create than /users, because I don't want to possible conflate another, currently unknown type of POST request for the same API endpoint. This will probably result in “enterprise-y” naming conventions for this project.

  • I really like the hourly journal. I got this idea from Adora Cheung's YC Startup School video on time management, where she mentions how bullshit metrics can easily conflate with the two metrics that startups should actually look for: number of users talked to, and progress of product. I've found myself much more engaged during the day, and I like having an audit log I can go back and review. Maybe I'll even keep this going after I go back to work; who knows.

Subscribe to my mailing list