Chris Padilla/Blog
My passion project! Posts spanning music, art, software, books, and more. Equal parts journal, sketchbook, mixtape, dev diary, and commonplace book.
- Music Teachers Expand Dreams. On Seth Godin's "Stop Stealing Dreams"
- 250 Box Challenge Complete. Victory post after completing the drawabox namesake challenge.
- Creative Legacy. On originality and creativity.
- Folk Music. On creating online, my personal favorite.
- Verifying it's an art post (based on which folder I select to save the image to)
- Creating a file with the post date in the filename (I usually put them together on Fridays, and share on Saturday)
- Writing details from the command line, such as title, alt text, and post body.
- Look up the user in the DB
- Verify the password
- Handle Matches
- Handle Mismatch
- Verify with the previous framework/library the encryption method
- If possible, transfer over the code/libraries used
- Wrap it in a
checkPassword()
function. - Designed to be secure by default and encourage best practices for safeguarding user data
- Uses Cross-Site Request Forgery Tokens on POST routes (sign in, sign out)
- Default cookie policy aims for the most restrictive policy appropriate for each cookie
- When JSON Web Tokens are enabled, they are encrypted by default (JWE) with A256GCM
- Auto-generates symmetric signing and encryption keys for developer convenience
- Optimize my images locally (something Cloudinary already automates, but I do by hand for...fun?!)
- Open up the Cloudinary portal
- Navigate to the right directory
- Upload the image
- Copy the url
- Paste the image into my markdown file
- Optionally add optimization tag if needed
My Reading Year, 2023
From "The Black Swan" by Nassim Nicholas Taleb, Via Anne-Laure Le Cunff:
"The goal of an antilibrary is not to collect books you have read so you can proudly display them on your shelf; instead, it is to curate a highly personal collection of resources around themes you are curious about. Instead of a celebration of everything you know, an antilibrary is an ode to everything you want to explore."
I discovered the idea of an antilibrary this month from artist Jake Parker's excellent blog and newsletter. That feels more true than ever before for me. I have a pile of books I haven't made it to yet this year that won't be on my list (the true antilibrary!) And those that made it into my hands and onto this list very closely track what I've been working on, learning, and dreaming about this year.
I'm historically a pragmatic reader – spending hunks of time on self help. My 2022 reading year is a good representation of that.
This year, instead of saying I'll cut my reading all together, I spent time making reading just for fun again! Lots of comics fulfilled that, a few music books helped me play at the instrument, and some of my non fiction reading helped me look at my work and day to day more playfully.
Favorites

Form & Essence
I took an arts admin class with Matt back at UT. Matt is such an amazing embodiment of the servant leader, and this book feels like I'm getting to take the best bits of his class all over again. I liked it so much, I said these words about it on Goodreads:
"Matthew Hinsley, with a musician's sensitivity, brings the magic of balancing both the tangible and the spiritual off of the artist's canvas and into our daily lives. Form & Essence is an antidote to the day-to-day focus on what's quantifiable. Without asking the reader to forego the material, Hinsley warmly invites the reader to look beyond at all at the things that truly matter — relationships, storytelling, and building a life around character. This book feels like a wonderful blending of Stephen Covey and Julia Cameron — fitting for the subject of the book. But Matt's own generosity and personality shine through clear as day. And his is a personality worth being with in its own right. Even if you only spend a bit of time with a few chapters, I can guarantee that you will see the world differently: with more lightness, with an eye for deepening your connections, and a gravitation towards the seemingly small things that truly matter in life.”

Terry Pratchett: A Life With Footnotes by Rob Wilkins
A humorous, authentic, and a heartfelt account by Rob Wilkins. I'm glad to have discovered that the author behind Discworld was just as colorful and down to earth as his stories. Further proof of how prolific and brilliant a writer Terry was. Rob's account of Terry's decline towards the end of his life was absolutely heart-wrenching. Though, as the book concludes, "Of all the dead authors in the world, Terry is the most alive." I had the pleasure of writing a few words after reading on inspiration and libraries.

Free Play: Improvisation in Life and Art
"The creative process is a spiritual path. This adventure is about us, about the deep self, the composer in all of us, about originality, meaning not that which is all new, but that which is fully and originally ourselves." A multi-faceted look at the act of making in the moment. Turned my impression of improvisation on its head by acknowledge that even the act of design and iteration is just as improvisatory as riffing off the cuff!

Fellowship of the Ring
My first read through!! I bopped between physical and audiobook. Rob Ingles is phenomenal, but the scene painting is so lush, I ended up wanting to sit down and take it all in with the book in hand. Warm characters, a fantastically grand adventure, and a world so fully realized, I'm not at all surprised to see how this remains a timeless classic. Rob Ingles reading and singing the audiobook version is the way to experience this.

Walt Disney's Mickey Mouse "Trapped On Treasure Island" by Floyd Gottfredson
Stunning adventure comics! I devoured the second volume early this year. I can't believe these were printed in the newspaper. The detail in the sets, the grand nature of the story — these strips culminate in a rich novel! Floyd Gottfredson was a major inspiration for Carl Barks, and it's easy to see why. Included are really nice essays giving historical context to these stories as well.

Painting As a Pastime
Thoughts on hobby making and painting from an enthusiastic hobbyist! Wonderful insight on the importance of keeping many creative pots boiling. It's also a beautiful love letter to the medium of color and light through painting. Very inspiring, the book motivated to keep planting my garden.
Music Books
Faber Elementary Methods. Just a superb elementary music method, I don't even care if it's written for 10 year olds! Engaging, perfectly paced, highly musical — I never feel overwhelmed and never feel bored. Each piece written by N Faber is such a treat. Combing all the supplementary books allows for a deep dive into each new topic. If you've never picked up an instrument and want to, my vote is piano and that you give these books a whirl.
Hal Leonard Classical Guitar. As someone who's played campfire guitar for the last few years, this book was really challenging. The pacing is very accelerated, with each piece taking a few leaps. The music is beautiful, but I wouldn't recommend this as a primary course of study.
Christopher Parkening Classical Guitar Method. This, on the other hand, is excellent! Similar to Faber, pieces build on one another and there's ample time to reinforce finger coordination in the left and right hand with several pieces. And for being so simple, plenty of the early pieces are so pretty. I'm not familiar with Parkening's playing yet, but his personal notes and the page spread photos are a nice touch to bring a personality to the journey.
Dan Haerle Jazz Voicings. The secret to jazz piano is using the puzzle pieces of conventional chord progressions and plugging them in. Dan Harle's book has the puzzle pieces. Knowing them makes playing standards WAY more fluid and fun!
Hal Leonard Jazz Piano Method by Mark Davis. Last year I enjoyed getting started with Mark Levine's Jazz Piano Book. Still being an early pianist, I needed something that was more my speed. Mark Davis' book fits the bill! Found early success reading lead sheets and improvising at the keyboard — an absolutely wild experience for a simple sax player like myself!
Art & Comics
Hayao Miyazaki by Jessica Niebel. Beautiful. Absolutely stunning selection of prints, stills, and write ups. An absolute must for any fan of these films.
Cartoon Animation with Preston Blair. I'll admit I haven't worked through the whole book just yet, but it's been a wonderful resource to help guide my practice. This won't necessarily teach you how to animate so much as give you a curriculum of what to work through with a few tips. For example, Blair touches on the importance of being able to use contraction in character development, breezes through topics on perspective, highlights what makes a character "cute" vs "screwball." All in all, a fantastic resource that's more of a road map rather than a step-by-step guide. Such is the way with most art books, I suppose!
Donald Duck The Old Castle Secret By Carl Barks. I wrote about Carl Barks earlier and this is my first intro to his cartoons. Hugely expressive, phenomenal storytelling, and plenty of genuine chuckles!
Calvin & Hobbes by Bill Watterson. WOW!! For a modern newspaper strip, these were so dynamic!! Compared to something like Garfield, it's so much fun to see how animated and expressive these strips were. The forward captures the spirit well: These strips are such a fantastic representation of what it felt to be a child, fluidly twisting between a colorful imagination and the world as is.
Cucumber Quest by Gigi D.G. . I missed this when it was in it's hey-day! The art here is stunningly gorgeous, the color design and beautiful locales keep me from turning the page and reading the dang story! The attitude of the series is very 2000s internet culture, so the new adventure has a strong sense of familiarity.
Are You Listening? by Tillie Walden. I've picked this one up a few times this year. I adore Walden's colors. Like Calvin and Hobbes, it seems like there's a back and forth between traveling in a dream and on the open road in the real world. Except the line is much thinner and much more lush.
Sonic the Hedgehog 30th Anniversary Celebration published by IDW. I came for the McElroy feature, but stayed for the incredible art. (And, of course, I grew up on the games, so y'know.) Just look at some of these pages and try to tell me that these aren't excellently paneled, posed, and colored. An absolute treat.
Dragon Ball by Akira Toriyama. I forgot how plain old silly the original series was! Z may be action packed, but this was all gags. The best part of reading these is getting to take in Toriyama's inventive and detailed vehicle design.
Fullmetal Alchemist by Hiromu Arakawa. I tried getting into the anime a couple of years ago, but I think the manga is the way for me. Really exciting artwork, Al and Ed look so stylish on every page!
Non Fiction
One Person/Multiple Careers: A New Model for Work/Life Success by Marci Alboher. I needed this book to tell me I'm not crazy! A great collection of interviews and case studies from people who's work isn't easily bound to one job. Great read for anyone tinkering to find fully meaningful work across multiple interests.
Make Your Art No Matter What: Moving Beyond Creative Hurdles by Beth Pickens. Austin Kleon and Beth Pickens had a great talk recorded online about making creative work, and it lead me to her book. Favorite take away: Creative people are those who need their practice so they can wholly show up in all other areas of their lives.
How to Fail at Almost Everything and Still Win Big by Scott Adams. I reread this earlier this year and still think about some of the essays. General life advise mixed with Adams' life story, including his navigation through bouts of focal dystonia. Entertaining and insightful!
The Gifts of Imperfection by Brené Brown. I regularly return to these guideposts, though this time around, it was important for me to really spend some time with the opening discussion around shame. Even after years of Brown's work being in the cultural conversation, this little volume still opens up new insight on authentic living on re-reading.
Also
The Alchemist by Paulo Coelho. A last-minute read over the holiday break! Miranda and I both wanted to read this together, and we've loved it! A beautiful journey that illustrates how much richer the world is when we pursue our own personal inspirations and callings, grand or simple. I read this in high school, having practically no life experience to draw on for all of the metaphors. Surprising to me: reading it now, the terrain covered in the book is largely familiar.
Odds and Ends
I wrote several articles for the blog this year! At some point, I had a few pieces complete, but not published. Eventually, I had a few too many creative projects spinning, and they fell by the wayside.
On the finished ones, I'm hitting the publish button, but am leaving the original date written. Here they are in list form so that these don't fall through the cracks. They make an interesting sample of where my head has been this year:
Merry Christmas!
Silent Night
Merry Christmas from our family’s oooooold piano!
Landscape Gestures
Generating Post Templates Programmatically with Python
I'm extending my quick script from a previous post on Automating Image Uploads to Cloudinary with Python. I figured — why stop there! Instead of just automating the upload flow and getting the url copied to my clipboard, I could have it generate the whole markdown file for me!
Form there, the steps that will be automated:
Here we go!
Reorganizing
First order of business is organizing the code. This was previously just a script, but now I'd like to encapsulate everything within a class.
In my index.py, I'll setup the class:
from dotenv import load_dotenv
load_dotenv()
import datetime
import cloudinary
import cloudinary.uploader
import cloudinary.api
import pyperclip
class ImageHandler():
"""
Upload Images to cloudinary. Generate Blog page if applicable
"""
def __init__(self, test: bool = False):
self.test = test
config = cloudinary.config(secure=True)
print("****1. Set up and configure the SDK:****\nCredentials: ", config.cloud_name, config.api_key, "\n")
Beneath, I'll wrap the previous code in a method called "upload_image()"
I'll still call the file directly to run the main script, so at the bottom of the file I'll add the code to do so:
if __name__ == '__main__':
ImageHandler().run()
So, on run, I want to handle the upload to Cloudinary. From the result, I want to get the image url back, and I want to know if I should start generating an art post:
def run(self):
image_url, is_art_image = self.upload_image()
print(is_art_image)
if is_art_image:
self.generate_blog_post(image_url=image_url)
To make that happen, I'm adding a bit of logic to image_upload
to check if I'm storing the image in my art folder:
def upload_image(self):
...
options = [
"/chrisdpadilla/blog/art",
"/chrisdpadilla/blog/images",
"/chrisdpadilla/albums",
]
is_art_image = selected_number == 0
...
selected_number = int(selected_number_input) - 1
is_art_image = selected_number == 0
...
return (res_url, is_art_image)
That tuple will then return string and boolean in a neat little package.
Now for the meat of it! I'll write out my generate_blog_post()
method.
Starting with date getting. Using datetime and timedelta, I'll be checking to see when the next Saturday will be (ART_TARGET_WEEKDAY is a constant set to 6 for Saturday):
def generate_blog_post(self, image_url: str = '') -> bool:
today = datetime.datetime.now()
weekday = today.weekday()
days_from_target = datetime.timedelta(days=ART_TARGET_WEEKDAY - weekday)
target_date = today + days_from_target
target_date_string = target_date.strftime('%Y-%m-%d')
Sometimes, I do this a week out, so I'll add some back and forth incase I want to manually set the date:
date_ok = input(f"{target_date_string} date ok? (Y/n): ")
if "n" in date_ok:
print("Set date:")
target_date_string = input()
print(f"New date: {target_date_string}")
input("Press enter to continue ")
From here, it's all command line inputs.
title = input("Title: ")
alt_text = input("Alt text: ")
caption = input("Caption: ")
And then I'll use a with statement to do my file writing. The benefit of using the with
keyword here is that it will handle file closing automatically.
md_body = SKETCHES_POST_TEMPLATE.format(title, target_date_string, alt_text, image_url, caption)
with open(BLOG_POST_DIR + f"/sketches-{target_date_string}.md", "w") as f:
f.write(md_body)
return True
Violà! Running through the command line now generates a little post! Lots of clicking and copy-and-pasting time saved! 🙌
Angels We Have Heard on High
Taeko Onuki
New Album — Ice 🧊
Music somewhere between ambient classical and winter lofi!
Purchase on 🤘 Bandcamp and Listen on 🙉 Spotify or any of your favorite streaming services!
Credentials Authentication in Next.js
Taking an opinionated approach, Next Auth intentionally limits the functionality available for using credentials such as email/password for logging in. The main limit is that this forces a JWT strategy instead of using a database session strategy.
Understandably so! The number of data leaks making headlines, exposing passwords, has been a major security issue across platforms and services.
However, the limitation takes some navigating when you are migrating from a separate backend with an existing DB and need to support older users that created accounts with the email/password method.
Here's how I've been navigating it:
Setup Credentials Provider
Following the official docs will get you most of the way there. Here's the setup for my authOptions in app/api/auth/[...nextAuth]/route.js
:
import CredentialsProvider from "next-auth/providers/credentials";
...
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
username: {label: 'Username', type: 'text', placeholder: 'your-email'},
password: {label: 'Password', type: 'password', placeholder: 'your-password'},
},
async authorize(credentials, req) {
...
},
}),
Write Authorization Flow
From here, we need to setup our authorization logic. We'll:
async authorize(credentials, req) {
try {
// Add logic here to look up the user from the credentials supplied
const foundUser = await db.collection('users').findOne({'unique.path': credentials.email});
if(!foundUser) {
// If you return null then an error will be displayed advising the user to check their details.
return null;
// You can also Reject this callback with an Error thus the user will be sent to the error page with the error message as a query parameter
}
if(!foundUser.unique.path) {
console.error('No password stored on user account.');
return null;
}
const match = checkPassword(foundUser, credentials.password);
if(match) {
// Important to exclude password from return result
delete foundUser.services;
return foundUser;
}
} catch (e) {
console.error(e);
}
return null;
},
PII
The comments explain away most of what's going on. I'll explicitly note that here I'm using a try/catch block to handle everything. When an error occurs, the default behavior is for the error to be sent to the client and displayed. Even an incorrect password error could cause a Personally Identifiable Information (PII) error. By catching the error, we could log it with our own service and simple return null for a more generic error of "Failed login."
Custom DB Lookup
I'll leave explicit details out from here on how a password is verified for my use case. But, a general way you may approach this when migration:
Sending Passwords over the Wire?
A concern that came up for me: We hash passwords to the database, but is there an encryption step needed for sending it over the wire?
Short answer: No. HTTPS covers it for the most part.
Additionally, Next auth already takes many security steps out of the box. On their site, they list the following:
CSRF is the main concern here, and they have us covered!
Integrating With Other Providers
Next Auth also allows for using OAuth sign in as well as tokens emailed to the clients. However, it's not a straight shot. Next requires a JWT strategy, while emailing tokens requires a database strategy.
There's some glue that needs adding from here. A post for another day!
Fantasia!
Automating Image Uploads to Cloudinary with Python
There's nothing quite like the joy of automating something that you do over and over again.
This week I wrote a python script to make my life easier with image uploads for this blog. The old routine:
I can eliminate most of those steps with a handy script. Here's what I whipped up, with some boilerplate provided by the Cloudinary SDK quick start guide:
from dotenv import load_dotenv
load_dotenv()
import cloudinary
import cloudinary.uploader
import cloudinary.api
import pyperclip
config = cloudinary.config(secure=True)
print("****1. Set up and configure the SDK:****\nCredentials: ", config.cloud_name, config.api_key, "\n")
print("Image to upload:")
input1 = input()
input1 = input1.replace("'", "").strip()
print("Where is this going? (Art default)")
options = [
"/chrisdpadilla/blog/art",
"/chrisdpadilla/blog/images",
"/chrisdpadilla/albums",
]
folder = options[0]
for i, option in enumerate(options):
print(f'{i+1} {option}')
selected_number_input = input()
if not selected_number_input:
selected_number_input = 1
selected_number = int(selected_number_input) - 1
if selected_number <= len(options):
folder = options[selected_number]
res = cloudinary.uploader.upload(input1, unique_filename = False, overwrite=True, folder=folder)
if res.get('url', ''):
pyperclip.copy(res['url'])
print('Uploaded! Url Coppied to clipboard:')
print(res['url'])
Now, when I run this script in the command line, I can drag an image in, the script will ask where to save the file, and then automatically copy the url to my clipboard. Magic! ✨
A couple of steps broken down:
Folders
I keep different folders for organization. Album art is in one. Blog Images in another. Art in yet another. So first, I select which one I'm looking for:
print("Where is this going? (Art default)")
options = [
"/chrisdpadilla/blog/art",
"/chrisdpadilla/blog/images",
"/chrisdpadilla/albums",
]
folder = options[0]
for i, option in enumerate(options):
print(f'{i+1} {option}')
selected_number_input = input()
and later on, that's passed to the cloudinary API as a folder:
if not selected_number_input:
selected_number_input = 1
selected_number = int(selected_number_input) - 1
if selected_number <= len(options):
folder = options[selected_number]
res = cloudinary.uploader.upload(input1, unique_filename = False, overwrite=True, folder=folder)
Copying to clipboard
Definitely the handiest, and it's just a quick install to get it. I'm using pyperclip to make it happen with this one liner:
if res.get('url', ''):
pyperclip.copy(res['url'])
Clementi - Sonatina in F Maj Exposition
Note to self: don't wait until a couple of weeks after practicing something to record 😅
Blue Hair, Don't Care
Next Auth Custom Session Data
I've been tinkering with Next Auth lately, getting familiar with the new App Router and React Server Components. Both have made for a big paradigm shift, and a really exciting one at that!
With all the brand new tech, and with many people hard at work on Next Auth to integrate with all of the new hotness, there's still a bit of transition going on. For me, I found I had to do a bit more digging to really setup Next Auth in my project, so here are some of the holes that ended up getting filled:
Getting User Data from DB through JWT Strategy
When you use a database adapter, Next auth automates saving and update user data. When migrating an existing app and db to Next auth, you'll likely want to handle the db interactions yourself to fit your current implementation.
Here's what the authOptions looked like for an OAuth provider:
export const authOptions = {
// adapter: MongoDBAdapter(db),
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
session: {
jwt: true,
maxAge: 30 * 24 * 60 * 60,
},
}),
],
secret: process.env.NEXTAUTH_SECRET,
};
Notice that I'm leaving the adapter above out and using the jwt strategy here.
There's a bit of extra work to be done here. The session will save the OAuth data and send it along with the token. But, more than likely, you'll have your own information about the user that you'd like to send, such as roles within your own application.
To do that, we need to add a callbacks object to the authOptions with a jwt
and session
methods:
async jwt({token, user}) {
if(user) {
token.user = user;
const {roles} = await db.users.findOne(query)
token.roles = roles;
}
return token;
},
async session({session, token}) {
if(token.roles) {
session.roles = token.roles;
}
return session;
},
So there's a bit of hot-potato going on. On initial sign in, we'll get the OAuth user data, and then reference our db to find the appropriate user. From there, we pass that to the token, which is then extracted into the session later on.
Once that's set, you'll want to pass these authOptions
in every time you call getServerSession
so that these callbacks are used to grab the dbUser
field. Here's an example in a server action:
import React from 'react';
import {getServerSession} from 'next-auth';
import { authOptions } from '@api/[...nextauth]/route';
import Button from './Button';
export default async function ServerActionPage() {
const printName = async () => {
'use server';
const session = await getServerSession(authOptions);
console.log(session);
return session?.user?.name || 'Not Logged In';
};
return (
<div className="m-20">
<Button action={printName} />
</div>
);
}
When that's logged, we'll get the OAuth user info and the roles we passed in from our db:
{
user: {...}
roles: [...]
}