rockyourcode: Verify Your Online Accounts With Keyoxide, Use Environment Variables With VuePress 2, Notes on “Building a Pragmatic Unit Test Suite”, My First T3 App
Hello 👋! Thanks for subscribing.
Here are my latest articles:
Verify Your Online Accounts With Keyoxide
Published on: 2022-11-16
tags: Tools
Keyoxide is a privacy-friendly t1. ool to create and verify decentralized online identities.
Just like passports for real life identities, Keyoxide can be used to verify the online identity of people to make sure one is interacting with whom they are supposed to be and not imposters. Unlike real life passports, Keyoxide works with online identities or "personas", meaning these identities can be anonymous and one can have multiple separate personas to protect their privacy, both online and in real life.
The tool helps you to verify your online profiles like GitHub, Mastodon, dev.to and others.
Here is my profile.
Acknowledgements:
I used Bill Rawlinson's guide as a reference.
During setup, I encountered some pitfalls. This article is an attempt to clarify and document the process by rewriting the original source.
In this article you'll learn:
- how to setup a GPG key and what to do to use it with keyoxide
- how to verify your Mastodon, dev.to & GitHub account
- how to setup a keyoxide account
What Do I Need?
Keyoxide is a weird beast.
It wasn't clear to me how to get a keyoxide account.
Do I need to sign up somewhere?
The answer is: No, you don't need to sign up for keyoxide.
But you need to create a GPG key pair and you need to upload it to keys.openpgp.org.
You'll also need a valid email address.
This email address will be public on the Keyoxide website.
The GPG key pair needs a secure passphrase, so a password manager is recommended.
GPG Setup
First, we need GnuPGP on our local machine.
On macOs, you can install it via homebrew.
In your terminal, type:
brew install gnupg
Arch Linux (with yay):
yay -S gnupg
Create a Key Pair
Again, you need to use the terminal:
macOs:
gpg --full-generate-key
(Arch) Linux:
gpg --full-gen-key
- Choose
RSA and RSA
(option 1). - Keysize: 4096
- Expiration date: 2y (2 years, you can extend the expiration)
- Real name: you don't need to use your real name, but this is the handle which will appear on your keyoxide side
- email: use an email address that works and that you have access to (you can also add more email addresses later)
- optional comment: leave blank
- secure passphrase: use your password manager to create a password (and save it in your password manager together with the email address!)
You'll be asked to generate some randomness, so you can move your cursor to help GPG to create your key.
Get Your Fingerprint
In your terminal, run the following command:
gpg -k
The answer will look like similar to this:
pub rsa4096 2020-07-01 [SC]
<HERE IS YOUR FINGERPRINT>
uid [ultimate] My name <valid@email.address>
Your keyoxide URL will be https://keyoxide.org/FINGERPRINT
. It will not work right now, but we'll come back to it later.
I know, it is confusing.
More info on the Keyoxide website.
Add Your Accounts
Mastodon
For Mastodon, you'll need to set profile metadata.
- go to your profile in Mastodon (
https://<your instance url>/profile/settings/profile
) - edit your profile
- scroll down to "Profile metadata"
- add a label "keyoxide"
- as content add your keyoxide URL (
https://keyoxide.org/FINGERPRINT
)
Read more about Mastodon on the keyoxide docs.
GitHub
Create a new public GitHub gist.
Important: The file name must be openpgp.md
!
The description for the file can be whatever you like.
As content for the file, add the following:
[Verifying my cryptographic key:openpgp4fpr:FINGERPRINT]
Read more about GitHub on the keyoxide docs.
dev.to
Make a new blog post. The title does not matter, I chose "Keyoxide Proof" (see example).
The content of the post is the following:
[Verifying my keyoxide cryptographic key: https://keyoxide.org/FINGERPRINT]
Yes, it's a public post which will look strange.
Add All Your Proofs to Your Gpg Key
Now we need to go back to the command-line.
gpg --edit-key YOUR_EMAIL_ADDRESS
or
gpg --edit-key FINGERPRINT
Replace with your email address.
This will open a command prompt.
- type:
uid 1
(to select your user ID) - type:
notation
- enter the notation:
proof@ariadne.id=https://URL_TO_YOUR_GIST
(replace with your Gist URL) - you will be asked for your passphrase, enter it now (you used a password manager, right?)
- repeat the process for your other accounts, type
notation
again - enter the notation:
proof@ariadne.id=https://dev.to/YOUR_USERNAME/BLOG_POST_URL
(replace with your dev.to blog post URL) - same again for all other accounts
For example, the notation for Mastodon is:
proof@ariadne.id=https://YOUR_MASTODON_INSTANCE/@YOUR_USERNAME
Example:
proof@ariadne.id=https://hachyderm.io/@sbr
How to Show Your Notations for a Key?
gpg --edit-key FINGERPRINT
Show a list of user IDs to find the index, select it:
list
uid N # for example: uid 1
Show a list of notations:
showpref
How to Delete a Notation?
What happens if you made a mistake?
To delete an existing notation, you need to add it again, but with a minus symbol:
-key=value
Upload Your GPG Key
Finally, you need to upload your public key to the OpenPGP.org.
First, we'll need to find a way to export our public key for upload. In your terminal, type the following:
gpg --armor --export YOUR_EMAIL_ADRESS > pubkey.asc
Replace with your email address. Don't forget the greater than sign (>
).
Open the OpenPGP website and upload the pubkey.asc
.
Now you can go to your keyoxide URL and check if it works.
It might take a few minutes, but the process is reasonably fast.
What Happens if I Want to Add More Accounts?
If you later want to add more accounts, you can go through the process again.
First, find a way to way to create a proof, then edit the GPG key.
Upload the key.
Keyoxide will pick up the changes.
How Can I Export My Key Pair?
If you want to backup your key pair, you can read this article on how to export both the public and private key.
Links
- Setting Up Keyoxide Profile on a Mac!
- Mastodon: Profile Metadata
- GnuPG
- How to export and import keys with GPG
Use Environment Variables With VuePress 2
Published on: 2022-11-07
tags: Vue.js, JavaScript
For $DAYJOB
I had to build a new feature for our internal VuePress 2 documentation.
I needed to fetch data from an API that needs an API token. That's why I wanted to use environment variables to store the token, both locally as well in our CI pipeline.
Surprisingly this was hard to figure out. It didn't help that I've never worked with Vue.js or VuePress. The documentation was of limited help.
For example, I stumbled upon the famous error when trying to use process.env
in a Vue component:
process is not defined
I'll show you how you can use environment variables in VuePress in the following article.
Goal
We want to be able to use a local file called .env
(or something similar like .env.local
) to store sensitive data.
Example content of the .env
file:
GOOGLE_API_TOKEN='abcdefghi12345'
We want to be able to use this token in the JavaScript part of Vue.
Example:
<script>
import fetch from 'cross-fetch'
async function fetchData() {
const response = await fetch('some-url', {
method: 'GET',
headers: {
// here we need our token 👇
Authorization: `Token token=`,
},
})
return response.json()
}
</script>
Note about cross-fetch
I've installed this library to be able to use the fetch API in Node.js during VuePress's generate step.
In the generate phase Node.js builds all pages, so we don't have the fetch API (browser-only until Node 18) at our disposal.
Problem
I couldn't find a guide in the VuePress documentation, the top StackOverflow question seems outdated, and the GitHub issue only got me 90% to the solution.
Solution
There is one library we need to install:
npm i dotenv
dotenv
is a popular JavaScript library which allows us to load environment variables from files. Exactly our use case!
Now we need to adjust the configuration for VuePress. You can read more about the Config file in the docs.
Add dotenv
to the Vue config file (config.js
):
// all other imports, e.g.
// import { registerComponentsPlugin } from '@vuepress/plugin-register-components';
import * as dotenv from 'dotenv'
dotenv.config()
The above code allows us to read our API token from the environment file. But how can we pass the variable to our frontend Vue component?
You cannot do this:
<script>
const TOKEN = process.env.GOGGLE_API_TOKEN
</script>
The Vue component in VuePress can be a client-side component. The browser can't access process
, that's Node.js-only.
You'll see this error:
process is not defined
VuePress has a hook to define global constants for the client code.
Add the following to your config object in config.js
:
export default defineUserConfig({
// previous configuration
// dest: 'public',
// lang: 'de-DE',
define: {
__TOKEN__: process.env.GOOGLE_API_TOKEN,
},
})
Now you can do the following in your Vue component:
<script>
import fetch from 'cross-fetch'
async function fetchData() {
const response = await fetch('some-url', {
method: 'GET',
headers: {
// here we can access the global constant 👇
Authorization: `Token token=${__TOKEN__}`,
},
})
return response.json()
}
</script>
The __TOKEN__
variable will "magically" work.
Summary
Here we have a working solution. Maybe there's a better way.
I don't like to use global constants. If you work with the Vue component it's not clear where the variable comes from.
But that's at least a working solution.
Links
Notes on “Building a Pragmatic Unit Test Suite”
Published on: 2022-10-30
tags: Testing
Here are some notes on the course “Building a Pragmatic Unit Test Suite” by Vladimir Khorikov.
Goals and Guidelines
Unit tests help with confidence: you know that changes don't break functionality.
Not all unit tests are equal.
Coverage metrics are problematic: you can work around them, for example, by writing assertion-free unit tests.
Coverage metrics are a good negative indicator, but 100% test coverage is impractical.
Test are code, and you also have to pay a maintenance cost for your tests.
What makes a unit test valuable?
- carefully choose code to test
- use the most valuable tests only
A good unit test:
- has a high chance of catching a regression error
- has a low chance of producing a false positive
- provides fast feedback
- has low maintenance cost
Testing trivial code is not worth the cost.
Decouple tests from implementation details as much as possible.
Spend most of the time on testing business logic.
Styles of Unit Testing
- output-based verification (functional style)
- state verification
- collaboration verification (uses test doubles)
Hexagonal Architecture
_image from Wikipedia_
Implementation Detail
Public API is the surface area that you can access from outside a class.
What are the requirements?
- address an immediate goal of the client code
- address that goal completely
Look at the client code: if it uses more than 1 operation to achieve a single goal, the class is leaking implementation details.
Note: Neighboring classes might be aware of implementation details.
Example: the Root Entity of an Aggregate (Domain Driven Design) might know about implementation details of the Entities.
Communication inside a hexagon is implementation detail.
Between hexagons a public API of the hexagon exist (contract).
Styles
- functional style: has no state, easy to maintain, offers the best protection against false positive
- state verification: should verify through public API, reasonable maintenance cost
- collaboration verification: within the hexagon lots of false positives; between hexagons more stable
Black-Box Testing Vs. White-Box Testing
- black-box testing: testing without knowing the internal structure
- white-box testing: testing the internal structure
Adhere to black-box testing as much as possible.
Business Requirements
Does the test verify a business requirement?
- view your code from the end user's perspective
- verify its observable behavior
Integration Tests
- test data cleanup: wipe out all data before test execution
Unit Testing Anti-Patterns
- private methods: if needed expose the hidden abstraction by extracting a new concept
- expose state getters: test the observable behavior only
- leaking domain knowledge to tests: use property-paced testing, or verify end result
- code pollution (introduce additional code just to enable unit testing)
- overriding methods in classes-dependencies: violates single-repository-principle, instead split functionality into different pieces
- non-determinism in tests: try to avoid testing async code (separate code into async/sync), use Tasks
Links
My First T3 App
Published on: 2022-10-25
tags: TypeScript
tRPC is the hottest new thing in the TypeScript ecosystem: build end-to-end type-safe APIs without the overhead of GraphQL.
tRPC is a protocol to expose a function of your backend to your frontend using TypeScript type definitions.
No code generation required. You write both your backend and your frontend with TypeScript and share the types.
tRPC is framework-agnostic.
Create-t3-app is build on top of tRPC. It offers an opinionated starter template that helps with building a complete web application with Next.js and Prisma.
This blog post chronicles my journey in creating my first T3 app. Let's see how the T3 stack works!
Create Application
pnpm dlx create-t3-app@latest
The command guides you through the installation process and allows you to choose a few options (trpc, prisma, next-auth, tailwind).
I am happy to see that the command also works with pnpm out of the box.
The command bootstraps the application. At the end of the process, there is a hint on what commands to run:
cd my-t3-app
pnpm install
pnpm prisma db push
pnpm dev
The project also offers a README
file with minimal information to get you started.
Prisma
My application should show cat pictures because the internet loves cats.
Let's adjust the Prisma schema:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
+model Cat {
+ id String @id @default(cuid())
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ imageUrl String
+}
This looks like a minimal example for a first application. Run pnpm exec prisma migrate dev --name add_cat_model
.
tRPC Router
My next instinct is to hook up the trpc router. The project comes with an example router in src/server/router/example.ts
. I'll adjust that to be a cat router.
The router uses zod, a schema-validation library, to build a router.
The example query has an input parameter of the String type.
For my case, I want a random cat picture, so no input is needed. Can I just delete the input parameter and return a random cat?
Before:
import { createRouter } from './context'
import { z } from 'zod'
export const exampleRouter = createRouter()
.query('hello', {
input: z
.object({
text: z.string().nullish(),
})
.nullish(),
resolve({ input }) {
return {
greeting: `Hello ${input?.text ?? 'world'}`,
}
},
})
.query('getAll', {
async resolve({ ctx }) {
return await ctx.prisma.example.findMany()
},
})
After:
import { createRouter } from './context'
import { Cat } from '@prisma/client'
export const catRouter = createRouter()
.query('random', {
async resolve({ ctx }) {
const randomCats = await ctx.prisma.$queryRaw<Cat[]>`SELECT id, imageUrl
FROM Cat
ORDER BY RANDOM()
LIMIT 1`
return randomCats[0]
},
})
.query('getAll', {
async resolve({ ctx }) {
return await ctx.prisma.cat.findMany()
},
})
I use a raw SQL query to retrieve a random cat from the database and add a typing for Cat[]
.
That's not pretty and does not give me the advantage of using the schema validator, but Prisma doesn't implement getting a random record. So raw SQL it is!
The raw query returns an array in any case, so we select the first element and return it.
Seed Script
Before I try to hook up the frontend, I remember that I don't have any example data in my database.
Luckily, the Prisma documentation can help me.
Add a new entry to package.json
:
{
"prisma": {
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
}
}
Create a new seed script in the prisma
folder (prisma/seed.ts
):
import { PrismaClient } from '@prisma/client'
import { fetch } from 'next/dist/compiled/@edge-runtime/primitives/fetch'
const prisma = new PrismaClient()
async function main() {
const requests = Array(10)
.fill('https://aws.random.cat/meow')
.map((url) => fetch(url))
Promise.all(requests)
// map array of responses into an array of response.json() to read their content
.then((responses) => Promise.all(responses.map((r) => r.json())))
// insert all responses as imageUrl
.then((cats) =>
cats.forEach(
async (cat) => await prisma.cat.create({ data: { imageUrl: cat.file } })
)
)
}
main()
.then(async () => {
await prisma.$disconnect()
})
.catch(async (e) => {
console.error(e)
await prisma.$disconnect()
process.exit(1)
})
I fetch ten image URLs from an API that offers random cat images and insert them into the database. Quite ugly, but it works.
In my terminal, I run type the following command:
pnpm exec prisma db seed
Success!
Hook Up the Client
Finally, we can try to show this data on the browser.
After ripping out the example router and replacing it with my cat router, I check src/pages/index.tsx
.
It has some boilerplate which I adjust to my needs:
import type { NextPage } from 'next'
import Head from 'next/head'
import Image from 'next/image'
import { trpc } from '../utils/trpc'
const Home: NextPage = () => {
const { data: cat } = trpc.useQuery(['cat.random'])
return (
<div style=display: 'grid', placeItems: 'center'>
<Head>
<title>T3 Cats</title>
<meta name="T3 cats" content="Generated by create-t3-app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div>
<h1 style=textAlign: 'center'>
Create <span>T3</span> App
</h1>
<section>
<div>
{cat ? (
<Image
src={cat.imageUrl}
alt={`random cat ${cat.id}`}
layout={'fixed'}
width={300}
height={300}
/>
) : (
<p>Loading...</p>
)}
</div>
</section>
</div>
</div>
)
}
export default Home
That was surprisingly easy, especially if you are familiar with Prisma.
First Impressions
The starter template does a good job on guiding you through the process.
The examples are enough to paint a broad picture on how trpc with Next.js works. Familiarity with prisma is assumed.
You might need to consult the Prisma documentation, trpc is almost self-declaratory, Prisma is not.
Links
Thank you for reading my blog.