Nov 2022—In which the author learns to measure twice, cut once
I wanted to automatically add certain media from my Google Photos account to a particular album. I used replit.com to code and run the server, set up a barebones OAuth flow to store an access token for offline use, but was stymied at the finish line because I didn't read the Google Photos API documentation in detail and it doesn't support what I was trying to achieve. Oops. RTFM, folks. Read it before you need it.
I love capturing the moment with my camera, but photos always feel flat when the scenery in front of me has so much life and motion in it. Instead I've been taking ~10-second videos - held still like a photo, but with subtle movement of elements in the scene and gentle background noise. I call them "vignettes".
I take a fair amount of these over the weeks, especially while traveling but also while out riding my bike or a nice moment on the way to work. I wanted to use the Google Photos API to automatically collect all my vignette videos, making it easy to flip through them and watch the seasons go by.
To achieve this I needed a headless machine that could operate this behaviour independently. With side projects I aim for ultra-minimal setups, particularly for infrastructure which isn't a strength of mine. I thought I'd give replit.com a try since I understood it to be an all-in-one tool for getting code out into the world, and I was particularly interested to try using it from my iPad. I was able to get a Node + Express server up and running pretty quickly.
There was a lot I liked about Replit. In terms of raw time, going from having the idea → writing code → server accessible at a URL, probably the fastest overall turnaround I've ever had on a project. Write NodeJS code, click "Run", see your app in a browser.
I used Replit's database and secrets features for a few different things, and they worked nicely. Like Codepen, the free account means your code is all public. I was always a bit nervous I'd accidentally publish something unintentional in the codebase, like an API key or PII, but for a hacky proof of concept it didn't feel worth paying for privacy.
Another infrastructure solution for this could have been serverless functions. I've poked into offerings for that before and found them confusing and difficult to get going with. I'm sure they're super helpful once you're up and running, but the replit developer experience made this experiment dead easy.
To make this happen, there were a few distinct requirements.
Replit servers shut themselves down 1 hour after their last web request so I wouldn't be able to use cron
to run the command once a day. They spin up again when requested, and from Googling it seems common to use Uptime Robot to ping a Replit URL. This will trigger code execution on the server, which is what we want.
As long as the triggering web request is only used to kick off an async job, without relying on that request to provide any authorisation or providing back any information besides the state of the job, then it should be safe to leave as a public URL.
Working with Google APIs requires an OAuth access token. Thanks to Tom Nagle's article Google OAuth With Node.js — Without Passport, I finally get OAuth. There is a simple core you can extract, a few functions that perform the dance steps necessary to ask Google APIs for data.
There are plenty of NPM modules which make setting up an OAuth connection easy (see Passport), BUT most of these packages assume you want to use OAuth to allow anyone to log into your web app. They expect you to use Express sessions, and have a database to store user information and session tokens.
My app has one user. I want the least amount of complexity possible. I found the tools overwhelming because I would've needed to use everything in a non-standard way. I didn't understand the underlying core of OAuth very well, which made that challenging.
My OAuth callback endpoint is publicly accessible, but my Google API project only allows my email address to authenticate so it's totally non-functional for anyone else. I generate an 'offline' access token which I store in the replit database. The business logic sync script looks for that access token to make authenticated requests to the Google Photos API. I didn't get as far as setting this up, but the system would be able to use a 'refresh' token when the original expires to allow access for quite a long time. I need to go through the Google OAuth flow in the browser once, but after that the automated sync should have the access it needs.
The business logic isn't very complex. Once per day:
Unfortunately, because I did all of this work in order, I realised too late that #3.2 was only half-possible and #3.3 impossible. I made assumptions about what the Google Photos API would provide.
Note that you can only add media items that have been uploaded by your application to albums that your application has created.
💀
My application isn't uploading the images—I'm scanning things uploaded by my phone. My API experiment can't complete the core task, the use case that got me into this. I suppose I could do something drastic like delete and re-upload the videos but that seems over the top. Feels risky, like it could mess up my Google Photos timeline somehow.
Conclusion: I halted the project here. Was a fun experiment, but not everything works out. Would be interested to hear if you have a sensible, minimal way I can solve this problem.