mirror of
https://github.com/jbranchaud/til
synced 2026-01-18 06:28:02 +00:00
Compare commits
38 Commits
947a8995e3
...
1fce1af3d7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fce1af3d7 | ||
|
|
c16d80fd94 | ||
|
|
edf38308da | ||
|
|
dc7159c16c | ||
|
|
33f780a69f | ||
|
|
dfe9c002ee | ||
|
|
3e34636d80 | ||
|
|
dcef57d344 | ||
|
|
6580393b7a | ||
|
|
17d7f0933b | ||
|
|
e4abc56f4c | ||
|
|
43ea7acd74 | ||
|
|
d7d331b688 | ||
|
|
b743dc2ac0 | ||
|
|
4a72c63e42 | ||
|
|
431507fd0e | ||
|
|
fb153f35bf | ||
|
|
f4ba6a9ef7 | ||
|
|
0dee39d3c5 | ||
|
|
5ebdd9a1a9 | ||
|
|
33c5cd748f | ||
|
|
ff515c8d6a | ||
|
|
fad36e0691 | ||
|
|
4d1d8e7134 | ||
|
|
567637497c | ||
|
|
028b76ba6b | ||
|
|
1934c8f63e | ||
|
|
e5a003dbaf | ||
|
|
ab9d2b5bf6 | ||
|
|
aa00c55b06 | ||
|
|
cc858382d8 | ||
|
|
dbb8c585c1 | ||
|
|
f25064031f | ||
|
|
cfbe640eb0 | ||
|
|
bf04dfcca5 | ||
|
|
24b1b02d52 | ||
|
|
8ef2cfdc69 | ||
|
|
bc767a0ad3 |
43
README.md
43
README.md
@@ -10,7 +10,7 @@ pairing with smart people at Hashrocket.
|
||||
|
||||
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
|
||||
|
||||
_1455 TILs and counting..._
|
||||
_1488 TILs and counting..._
|
||||
|
||||
---
|
||||
|
||||
@@ -19,6 +19,7 @@ _1455 TILs and counting..._
|
||||
* [Ack](#ack)
|
||||
* [Amplify](#amplify)
|
||||
* [Ansible](#ansible)
|
||||
* [Astro](#astro)
|
||||
* [Brew](#brew)
|
||||
* [Chrome](#chrome)
|
||||
* [Clojure](#clojure)
|
||||
@@ -26,6 +27,7 @@ _1455 TILs and counting..._
|
||||
* [Deno](#deno)
|
||||
* [Devops](#devops)
|
||||
* [Docker](#docker)
|
||||
* [Drizzle](#drizzle)
|
||||
* [Elixir](#elixir)
|
||||
* [Gatsby](#gatsby)
|
||||
* [Git](#git)
|
||||
@@ -98,9 +100,15 @@ _1455 TILs and counting..._
|
||||
|
||||
- [Loop Over A List Of Dictionaries](ansible/loop-over-a-list-of-dictionaries.md)
|
||||
|
||||
### Astro
|
||||
|
||||
- [Generate Types For A Content Collection](astro/generate-types-for-a-content-collection.md)
|
||||
- [Markdown Files Are Of Type MarkdownInstance](astro/markdown-files-are-of-type-markdown-instance.md)
|
||||
|
||||
### Brew
|
||||
|
||||
- [Configure Brew Environment Variables](brew/configure-brew-environment-variables.md)
|
||||
- [Export List Of Everything Installed By Brew](brew/export-list-of-everything-installed-by-brew.md)
|
||||
- [List All Services Managed By Brew](brew/list-all-services-managed-by-brew.md)
|
||||
|
||||
### Chrome
|
||||
@@ -201,6 +209,11 @@ _1455 TILs and counting..._
|
||||
- [List Running Docker Containers](docker/list-running-docker-containers.md)
|
||||
- [Run A Basic PostgreSQL Server In Docker](docker/run-a-basic-postgresql-server-in-docker.md)
|
||||
|
||||
### Drizzle
|
||||
|
||||
- [Create bigint Identity Column For Primary Key](drizzle/create-bigint-identity-column-for-primary-key.md)
|
||||
- [Get Fields For Inserted Row](drizzle/get-fields-for-inserted-row.md)
|
||||
|
||||
### Elixir
|
||||
|
||||
- [All Values For A Key In A Keyword List](elixir/all-values-for-a-key-in-a-keyword-list.md)
|
||||
@@ -269,6 +282,7 @@ _1455 TILs and counting..._
|
||||
- [Auto-Squash Those Fixup Commits](git/auto-squash-those-fixup-commits.md)
|
||||
- [Caching Credentials](git/caching-credentials.md)
|
||||
- [Change The Start Point Of A Branch](git/change-the-start-point-of-a-branch.md)
|
||||
- [Check How A File Is Being Ignored](git/check-how-a-file-is-being-ignored.md)
|
||||
- [Checking Commit Ancestry](git/checking-commit-ancestry.md)
|
||||
- [Checkout Old Version Of A File](git/checkout-old-version-of-a-file.md)
|
||||
- [Checkout Previous Branch](git/checkout-previous-branch.md)
|
||||
@@ -318,6 +332,7 @@ _1455 TILs and counting..._
|
||||
- [List Untracked Files](git/list-untracked-files.md)
|
||||
- [List Untracked Files For Scripting](git/list-untracked-files-for-scripting.md)
|
||||
- [Move The Latest Commit To A New Branch](git/move-the-latest-commit-to-a-new-branch.md)
|
||||
- [Override The Global Git Ignore File](git/override-the-global-git-ignore-file.md)
|
||||
- [Pick Specific Changes To Stash](git/pick-specific-changes-to-stash.md)
|
||||
- [Pulling In Changes During An Interactive Rebase](git/pulling-in-changes-during-an-interactive-rebase.md)
|
||||
- [Push To A Branch On Another Remote](git/push-to-a-branch-on-another-remote.md)
|
||||
@@ -424,8 +439,10 @@ _1455 TILs and counting..._
|
||||
### Internet
|
||||
|
||||
- [Add Emoji To GitHub Repository Description](internet/add-emoji-to-github-repository-description.md)
|
||||
- [Analyze Your Website Performance](internet/analyze-your-website-performance.md)
|
||||
- [Check Your Public IP Address](internet/check-your-public-ip-address.md)
|
||||
- [Enable Keyboard Shortcuts In Gmail](internet/enable-keyboard-shortcuts-in-gmail.md)
|
||||
- [Exclude AI Overview From Google Search](internet/exclude-ai-overview-from-google-search.md)
|
||||
- [Exclude Whitespace Changes From GitHub Diffs](internet/exclude-whitespace-changes-from-github-diffs.md)
|
||||
- [Figure Out Your Public IP Address](internet/figure-out-your-public-ip-address.md)
|
||||
- [Focus The URL Bar](internet/focus-the-url-bar.md)
|
||||
@@ -435,6 +452,7 @@ _1455 TILs and counting..._
|
||||
|
||||
### Java
|
||||
|
||||
- [Ensure Resources Always Get Closed](java/ensure-resources-always-get-closed.md)
|
||||
- [Install Java On Mac With Brew](java/install-java-on-mac-with-brew.md)
|
||||
- [Run A Hello World Program In Eclipse](java/run-a-hello-world-program-in-eclipse.md)
|
||||
|
||||
@@ -448,6 +466,7 @@ _1455 TILs and counting..._
|
||||
- [Check If A Number Is Positive Or Negative](javascript/check-if-a-number-is-positive-or-negative.md)
|
||||
- [Check If File Exists Before Reading It](javascript/check-if-file-exists-before-reading-it.md)
|
||||
- [Check If Something Is An Array](javascript/check-if-something-is-an-array.md)
|
||||
- [Check Media Queries From JavaScript](javascript/check-media-queries-from-javascript.md)
|
||||
- [Check The Password Confirmation With Yup](javascript/check-the-password-confirmation-with-yup.md)
|
||||
- [Compare The Equality Of Two Date Objects](javascript/compare-the-equality-of-two-date-objects.md)
|
||||
- [Computed Property Names In ES6](javascript/computed-property-names-in-es6.md)
|
||||
@@ -493,6 +512,7 @@ _1455 TILs and counting..._
|
||||
- [List Top-Level NPM Dependencies](javascript/list-top-level-npm-dependencies.md)
|
||||
- [Load And Use Env Var In Node Script](javascript/load-and-use-env-var-in-node-script.md)
|
||||
- [Make The Browser Editable With Design Mode](javascript/make-the-browser-editable-with-design-mode.md)
|
||||
- [Make Truly Deep Clone With Structured Clone](javascript/make-truly-deep-clone-with-structured-clone.md)
|
||||
- [Matching A Computed Property In Function Args](javascript/matching-a-computed-property-in-function-args.md)
|
||||
- [Matching Multiple Values In A Switch Statement](javascript/matching-multiple-values-in-a-switch-statement.md)
|
||||
- [Mock A Function With Return Values Using Jest](javascript/mock-a-function-with-return-values-using-jest.md)
|
||||
@@ -590,6 +610,7 @@ _1455 TILs and counting..._
|
||||
- [Find The Process Using A Specific Port](mac/find-the-process-using-a-specific-port.md)
|
||||
- [Gesture For Viewing All Windows Of Current App](mac/gesture-for-viewing-all-windows-of-current-app.md)
|
||||
- [Insert A Non-Breaking Space Character](mac/insert-a-non-breaking-space-character.md)
|
||||
- [Keyboard Shortcuts For Interesting With Text Areas](mac/keyboard-shortcuts-for-interacting-with-text-areas.md)
|
||||
- [List All The Say Voices](mac/list-all-the-say-voices.md)
|
||||
- [Open Finder.app To Specific Directory](mac/open-finder-app-to-specific-directory.md)
|
||||
- [Quickly Type En Dashes And Em Dashes](mac/quickly-type-en-dashes-and-em-dashes.md)
|
||||
@@ -683,6 +704,7 @@ _1455 TILs and counting..._
|
||||
- [A Better Null Display Character](postgres/a-better-null-display-character.md)
|
||||
- [Add Foreign Key Constraint Without A Full Lock](postgres/add-foreign-key-constraint-without-a-full-lock.md)
|
||||
- [Add ON DELETE CASCADE To Foreign Key Constraint](postgres/add-on-delete-cascade-to-foreign-key-constraint.md)
|
||||
- [Add Unique Constraint Using Existing Index](postgres/add-unique-constraint-using-existing-index.md)
|
||||
- [Adding Composite Uniqueness Constraints](postgres/adding-composite-uniqueness-constraints.md)
|
||||
- [Aggregate A Column Into An Array](postgres/aggregate-a-column-into-an-array.md)
|
||||
- [Assumed Radius Of The Earth](postgres/assumed-radius-of-the-earth.md)
|
||||
@@ -702,6 +724,7 @@ _1455 TILs and counting..._
|
||||
- [Compute Hashes With pgcrypto](postgres/compute-hashes-with-pgcrypto.md)
|
||||
- [Compute The Levenshtein Distance Of Two Strings](postgres/compute-the-levenshtein-distance-of-two-strings.md)
|
||||
- [Compute The md5 Hash Of A String](postgres/compute-the-md5-hash-of-a-string.md)
|
||||
- [Concatenate Strings With A Separator](postgres/concatenate-strings-with-a-separator.md)
|
||||
- [Configure The Timezone](postgres/configure-the-timezone.md)
|
||||
- [Constructing A Range Of Dates](postgres/constructing-a-range-of-dates.md)
|
||||
- [Convert A String To A Timestamp](postgres/convert-a-string-to-a-timestamp.md)
|
||||
@@ -732,6 +755,7 @@ _1455 TILs and counting..._
|
||||
- [Duplicate A Local Database](postgres/duplicate-a-local-database.md)
|
||||
- [Edit Existing Functions](postgres/edit-existing-functions.md)
|
||||
- [Enable Logging Of Database Activity](postgres/enable-logging-of-database-activity.md)
|
||||
- [Enforce Uniqueness On Column Expression](postgres/enforce-uniqueness-on-column-expression.md)
|
||||
- [Escaping A Quote In A String](postgres/escaping-a-quote-in-a-string.md)
|
||||
- [Escaping String Literals With Dollar Quoting](postgres/escaping-string-literals-with-dollar-quoting.md)
|
||||
- [Export Query Results To A CSV](postgres/export-query-results-to-a-csv.md)
|
||||
@@ -784,11 +808,13 @@ _1455 TILs and counting..._
|
||||
- [Max Identifier Length Is 63 Bytes](postgres/max-identifier-length-is-63-bytes.md)
|
||||
- [Open Heroku Database In Postico From Terminal](postgres/open-heroku-database-in-postico-from-terminal.md)
|
||||
- [pg Prefix Is Reserved For System Schemas](postgres/pg-prefix-is-reserved-for-system-schemas.md)
|
||||
- [Postgres Does Not Support Unsigned Integers](postgres/postgres-does-not-support-unsigned-integers.md)
|
||||
- [Prepare, Execute, And Deallocate Statements](postgres/prepare-execute-and-deallocate-statements.md)
|
||||
- [Pretty Print Data Sizes](postgres/pretty-print-data-sizes.md)
|
||||
- [Pretty Printing JSONB Rows](postgres/pretty-printing-jsonb-rows.md)
|
||||
- [Prevent A Query From Running Too Long](postgres/prevent-a-query-from-running-too-long.md)
|
||||
- [Print The Query Buffer In psql](postgres/print-the-query-buffer-in-psql.md)
|
||||
- [Put Unique Constraint On Generated Column](postgres/put-unique-constraint-on-generated-column.md)
|
||||
- [Remove Not Null Constraint From A Column](postgres/remove-not-null-constraint-from-a-column.md)
|
||||
- [Renaming A Sequence](postgres/renaming-a-sequence.md)
|
||||
- [Renaming A Table](postgres/renaming-a-table.md)
|
||||
@@ -899,6 +925,7 @@ _1455 TILs and counting..._
|
||||
- [Custom Validation Message](rails/custom-validation-message.md)
|
||||
- [Customize Paths And Helpers For Devise Routes](rails/customize-paths-and-helpers-for-devise-routes.md)
|
||||
- [Customize The Path Of A Resource Route](rails/customize-the-path-of-a-resource-route.md)
|
||||
- [Define The Root Path For The App](rails/define-the-root-path-for-the-app.md)
|
||||
- [Delete Paranoid Records](rails/delete-paranoid-records.md)
|
||||
- [Demodulize A Class Name](rails/demodulize-a-class-name.md)
|
||||
- [Different Ways To Add A Foreign Key Reference](rails/different-ways-to-add-a-foreign-key-reference.md)
|
||||
@@ -964,6 +991,7 @@ _1455 TILs and counting..._
|
||||
- [Rescue From With A Separate Method](rails/rescue-from-with-a-separate-method.md)
|
||||
- [Respond With JSON Regardless of Content Type](rails/respond-with-json-regardless-of-content-type.md)
|
||||
- [Retrieve An Object If It Exists](rails/retrieve-an-object-if-it-exists.md)
|
||||
- [Rollback A Couple Migrations](rails/rollback-a-couple-migrations.md)
|
||||
- [Rollback A Specific Migration Out Of Order](rails/rollback-a-specific-migration-out-of-order.md)
|
||||
- [Rounding Numbers With Precision](rails/rounding-numbers-with-precision.md)
|
||||
- [Run A Rake Task Programmatically](rails/run-a-rake-task-programmatically.md)
|
||||
@@ -976,6 +1004,7 @@ _1455 TILs and counting..._
|
||||
- [Select Value For SQL Counts](rails/select-value-for-sql-counts.md)
|
||||
- [Serialize With fast_jsonapi In A Rails App](rails/serialize-with-fast-jsonapi-in-a-rails-app.md)
|
||||
- [Set A Timestamp Field To The Current Time](rails/set-a-timestamp-field-to-the-current-time.md)
|
||||
- [Set DateTime To Include Time Zone In Migrations](rails/set-datetime-to-include-time-zone-in-migrations.md)
|
||||
- [Set default_url_options For Entire Application](rails/set-default-url-options-for-entire-application.md)
|
||||
- [Set Schema Search Path](rails/set-schema-search-path.md)
|
||||
- [Set Statement Timeout For All Postgres Connections](rails/set-statement-timeout-for-all-postgres-connections.md)
|
||||
@@ -986,6 +1015,7 @@ _1455 TILs and counting..._
|
||||
- [Skip Validations When Creating A Record](rails/skip-validations-when-creating-a-record.md)
|
||||
- [Specify New Attributes For #find_or_create_by](rails/specify-new-attributes-for-find-or-create-by.md)
|
||||
- [Temporarily Disable strong_params](rails/temporarily-disable-strong-params.md)
|
||||
- [Temporarily Turn Off Pending Migrations Error](rails/temporarily-turn-off-pending-migrations-error.md)
|
||||
- [Test For A Subset Of Attributes On A Model](rails/test-for-a-subset-of-attributes-on-a-model.md)
|
||||
- [Test If An Instance Variable Was Assigned](rails/test-if-an-instance-variable-was-assigned.md)
|
||||
- [Test If deliver_later Is Called For A Mailer](rails/test-if-deliver-later-is-called-for-a-mailer.md)
|
||||
@@ -1212,6 +1242,7 @@ _1455 TILs and counting..._
|
||||
- [Pattern Match Values From A Hash](ruby/pattern-match-values-from-a-hash.md)
|
||||
- [Percent Notation](ruby/percent-notation.md)
|
||||
- [Precedence Of Logical Operators](ruby/precedence-of-logical-operators.md)
|
||||
- [Prevent erb_lint From Removing Opening Tags](ruby/prevent-erb-lint-from-removing-opening-tags.md)
|
||||
- [Print Data To Formatted Table](ruby/print-data-to-formatted-table.md)
|
||||
- [Question Mark Operator](ruby/question-mark-operator.md)
|
||||
- [Rake Only Lists Tasks With Descriptions](ruby/rake-only-lists-tasks-with-descriptions.md)
|
||||
@@ -1371,6 +1402,7 @@ _1455 TILs and counting..._
|
||||
- [Command Line Length Limitations](unix/command-line-length-limitations.md)
|
||||
- [Compare Two Variables In A Bash Script](unix/compare-two-variables-in-a-bash-script.md)
|
||||
- [Configure cd To Behave Like pushd In Zsh](unix/configure-cd-to-behave-like-pushd-in-zsh.md)
|
||||
- [Convert JPEG To PNG With ffmpeg](unix/convert-jpeg-to-png-with-ffmpeg.md)
|
||||
- [Convert SVG To Favicon](unix/convert-svg-to-favicon.md)
|
||||
- [Copying File Contents To System Paste Buffer](unix/copying-file-contents-to-system-paste-buffer.md)
|
||||
- [Copying Nested Directories With Ditto](unix/copying-nested-directories-with-ditto.md)
|
||||
@@ -1441,6 +1473,7 @@ _1455 TILs and counting..._
|
||||
- [List The Stack Of Remembered Directories](unix/list-the-stack-of-remembered-directories.md)
|
||||
- [Load Env Vars In Bash Script](unix/load-env-vars-in-bash-script.md)
|
||||
- [Look Through All Files That Have Been Git Stashed](unix/look-through-all-files-that-have-been-git-stashed.md)
|
||||
- [Make Direnv Less Noisy](unix/make-direnv-less-noisy.md)
|
||||
- [Map A Domain To localhost](unix/map-a-domain-to-localhost.md)
|
||||
- [Negative Look-Ahead Search With ripgrep](unix/negative-look-ahead-search-with-ripgrep.md)
|
||||
- [Occupy A Local Port With Netcat](unix/occupy-a-local-port-with-netcat.md)
|
||||
@@ -1641,6 +1674,7 @@ _1455 TILs and counting..._
|
||||
- [Swap Occurrences Of Two Words](vim/swap-occurrences-of-two-words.md)
|
||||
- [Swapping Split Windows](vim/swapping-split-windows.md)
|
||||
- [Swap The Position Of Two Split Windows](vim/swap-the-position-of-two-split-windows.md)
|
||||
- [Switch Moving End Of Visual Selection](vim/switch-moving-end-of-visual-selection.md)
|
||||
- [Tabs To Spaces](vim/tabs-to-spaces.md)
|
||||
- [The Vim Info File](vim/the-vim-info-file.md)
|
||||
- [Toggle Absolute And Relative Paths In BufExplorer](vim/toggle-absolute-and-relative-paths-in-bufexplorer.md)
|
||||
@@ -1665,6 +1699,7 @@ _1455 TILs and counting..._
|
||||
- [Advance Through Search Results](vscode/advance-through-search-results.md)
|
||||
- [Enable Breadcrumbs For Version 1.26 Release](vscode/enable-breadcrumbs-for-version-126-release.md)
|
||||
- [Find The Location Of User Settings JSON File](vscode/find-the-location-of-user-settings-json-file.md)
|
||||
- [Jump To Problems In The Current File](vscode/jump-to-problems-in-the-current-file.md)
|
||||
- [Open An Integrated Terminal Window](vscode/open-an-integrated-terminal-window.md)
|
||||
- [Pop Open The Quick Fix Window](vscode/pop-open-the-quick-fix-window.md)
|
||||
- [Step Through Project-Wide Search Results](vscode/step-through-project-wide-search-results.md)
|
||||
@@ -1681,14 +1716,18 @@ _1455 TILs and counting..._
|
||||
|
||||
### Workflow
|
||||
|
||||
- [Add Hotkeys For Specific Raycast Extensions](workflow/add-hotkeys-for-specific-raycast-extensions.md)
|
||||
- [Add Subscriber To Kit Form Via API](workflow/add-subscriber-to-kit-form-via-api.md)
|
||||
- [Add Subtitles To Existing Mux Video Asset](workflow/add-subtitles-to-existing-mux-video-asset.md)
|
||||
- [Access 1Password Credential From CLI](workflow/access-1password-credential-from-cli.md)
|
||||
- [Change Window Name In iTerm](workflow/change-window-name-in-iterm.md)
|
||||
- [Configure Email Redirect With Cloudflare](workflow/configure-email-redirect-with-cloudflare.md)
|
||||
- [Convert An ePub Document To PDF On Mac](workflow/convert-an-epub-document-to-pdf-on-mac.md)
|
||||
- [Create A Local Sanity Dataset Backup](workflow/create-a-local-sanity-dataset-backup.md)
|
||||
- [Create A Public URL For A Local Server](workflow/create-a-public-url-for-a-local-server.md)
|
||||
- [Enable Dev Tools For Safari](workflow/enable-dev-tools-for-safari.md)
|
||||
- [Forward Stripe Events To Local Server](workflow/forward-stripe-events-to-local-server.md)
|
||||
- [Get URL For GitHub User Profile Photo](workflow/get-url-for-github-user-profile-photo.md)
|
||||
- [Get Your Public IP Address](workflow/get-your-public-ip-address.md)
|
||||
- [Import A Github Project Into CodeSandbox](workflow/import-a-github-project-into-codesandbox.md)
|
||||
- [Interactively Kill A Process With fkill](workflow/interactively-kill-a-process-with-fkill.md)
|
||||
@@ -1696,6 +1735,7 @@ _1455 TILs and counting..._
|
||||
- [Prune The Excess From node_modules](workflow/prune-the-excess-from-node-modules.md)
|
||||
- [Rotate An Image To Be Oriented Upright](workflow/rotate-an-image-to-be-oriented-upright.md)
|
||||
- [See Overlaps For A Set Of Time Zones](workflow/see-overlaps-for-a-set-of-time-zones.md)
|
||||
- [Send A Message To A Discord Channel](workflow/send-a-message-to-a-discord-channel.md)
|
||||
- [Set Recurring Reminders In Slack](workflow/set-recurring-reminders-in-slack.md)
|
||||
- [Toggle Between Stories In Storybook](workflow/toggle-between-stories-in-storybook.md)
|
||||
- [Update asdf Plugins With Latest Package Versions](workflow/update-asdf-plugins-with-latest-package-versions.md)
|
||||
@@ -1731,6 +1771,7 @@ _1455 TILs and counting..._
|
||||
|
||||
- [Add To The Path Via Path Array](zsh/add-to-the-path-via-path-array.md)
|
||||
- [Link A Scalar To An Array](zsh/link-a-scalar-to-an-array.md)
|
||||
- [Use A Space To Exclude Command From History](zsh/use-a-space-to-exclude-command-from-history.md)
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
56
astro/generate-types-for-a-content-collection.md
Normal file
56
astro/generate-types-for-a-content-collection.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Generate Types For A Content Collection
|
||||
|
||||
Let's say I'm using Astro to publish posts via markdown. One of the best ways
|
||||
to do that is as a _Content Collection_. The posts will live in `src/content`
|
||||
probably under a `posts` directory. Plus a config file will define the
|
||||
collection and specify validations for the frontmatter.
|
||||
|
||||
```typescript
|
||||
// src/content/config.ts
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
|
||||
const postsCollection = defineCollection({
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
tags: z.array(z.string())
|
||||
})
|
||||
});
|
||||
|
||||
export const collections = {
|
||||
'posts': postsCollection,
|
||||
};
|
||||
```
|
||||
|
||||
When I first add this to my project and get the collection, it won't know what
|
||||
the types are.
|
||||
|
||||
```astro
|
||||
---
|
||||
import { getCollection } from "astro:content";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const blogEntries = await getCollection("posts");
|
||||
// ^^^ any
|
||||
|
||||
return blogEntries.map((entry) => ({
|
||||
params: { slug: entry.slug },
|
||||
props: { entry },
|
||||
}));
|
||||
}
|
||||
---
|
||||
```
|
||||
|
||||
I can tell Astro to generate a fresh set of types for things like content
|
||||
collections by running the [`astro sync`
|
||||
command](https://docs.astro.build/en/reference/cli-reference/#astro-sync).
|
||||
|
||||
```bash
|
||||
$ npm run astro sync
|
||||
```
|
||||
|
||||
This updates auto-generated files under the `.astro` directory which get pulled
|
||||
in to your project's `env.d.ts` file.
|
||||
|
||||
All of these types will also be synced anytime I run `astro dev`, `astro
|
||||
build`, or `astro check`.
|
||||
53
astro/markdown-files-are-of-type-markdown-instance.md
Normal file
53
astro/markdown-files-are-of-type-markdown-instance.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Markdown Files Are Of Type MarkdownInstance
|
||||
|
||||
One of the things Astro excels at is rendering markdown files as HTML pages in
|
||||
your site. And at some point we'll want to access a listing of those markdown
|
||||
files in order to do something like display a list of them on an index page.
|
||||
For that, we'll use
|
||||
[`Astro.glob()`](https://docs.astro.build/en/reference/api-reference/#astroglob).
|
||||
|
||||
```typescript
|
||||
---
|
||||
const allPosts = await Astro.glob("../posts/*.md");
|
||||
---
|
||||
|
||||
<ul>
|
||||
{allPosts.map(post => {
|
||||
return <Post title={post.frontmatter.title} slug={post.frontmatter.slug} />
|
||||
})}
|
||||
</ul>
|
||||
```
|
||||
|
||||
This looks great, but we'll run into a type error on that first line:
|
||||
`'allPosts' implicitly has type 'any'`. We need to declare the type
|
||||
of these post instances that are being read-in by Astro.
|
||||
|
||||
These are of [type
|
||||
`MarkdownInstance`](https://docs.astro.build/en/reference/api-reference/#markdown-files).
|
||||
That's a generic though, so we need to tell it a bit more about the shape of a
|
||||
post.
|
||||
|
||||
```typescript
|
||||
import type { MarkdownInstance } from "astro";
|
||||
|
||||
export type BarePost = {
|
||||
layout: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
tags: string[];
|
||||
};
|
||||
|
||||
export type Post = MarkdownInstance<BarePost>;
|
||||
```
|
||||
|
||||
We can then update that first line:
|
||||
|
||||
```typescript
|
||||
const allPosts: Post[] = await Astro.glob("../posts/*.md");
|
||||
```
|
||||
|
||||
Alternatively, you can specify the generic on `glob`:
|
||||
|
||||
```typescript
|
||||
const allPosts = await Astro.glob<BarePost>("../posts/*.md");
|
||||
```
|
||||
48
brew/export-list-of-everything-installed-by-brew.md
Normal file
48
brew/export-list-of-everything-installed-by-brew.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Export List Of Everything Installed By Brew
|
||||
|
||||
If you're on a Mac using Homebrew to install various tools and utilities, there
|
||||
may come a time when you want a listing of what is installed.
|
||||
|
||||
Run this command:
|
||||
|
||||
```bash
|
||||
$ brew bundle dump
|
||||
```
|
||||
|
||||
It may take 10 or so seconds. When it is done, you'll have a `Brewfile` in your
|
||||
current directory.
|
||||
|
||||
Open it up and you'll see a bunch of lines like the following:
|
||||
|
||||
```
|
||||
tap "heroku/brew"
|
||||
tap "homebrew/bundle"
|
||||
tap "homebrew/services"
|
||||
tap "mongodb/brew"
|
||||
tap "planetscale/tap"
|
||||
tap "stripe/stripe-cli"
|
||||
brew "asdf"
|
||||
brew "bat"
|
||||
brew "direnv"
|
||||
brew "entr"
|
||||
brew "exa"
|
||||
brew "fd"
|
||||
brew "ffmpeg"
|
||||
brew "fx"
|
||||
brew "fzf"
|
||||
brew "gcc"
|
||||
brew "gh"
|
||||
brew "planetscale/tap/pscale"
|
||||
brew "stripe/stripe-cli/stripe"
|
||||
cask "1password-cli"
|
||||
vscode "ms-playwright.playwright"
|
||||
vscode "ms-vsliveshare.vsliveshare"
|
||||
vscode "prisma.prisma"
|
||||
```
|
||||
|
||||
Notice there are `tap`, `brew`, `cask`, and even `vscode` directives.
|
||||
|
||||
This is a file you could export and then run on a 'new' machine to install all
|
||||
the programs you're used to having available on your current machine.
|
||||
|
||||
[source](https://danmunoz.com/setting-up-a-new-computer-with-homebrew/)
|
||||
48
drizzle/create-bigint-identity-column-for-primary-key.md
Normal file
48
drizzle/create-bigint-identity-column-for-primary-key.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Create bigint Identity Column For Primary Key
|
||||
|
||||
Using the Drizzle ORM with Postgres, here is how we can create a table that
|
||||
uses a [`bigint` data
|
||||
type](https://orm.drizzle.team/docs/column-types/pg#bigint) as a primary key
|
||||
[identity
|
||||
column](https://www.postgresql.org/docs/current/ddl-identity-columns.html).
|
||||
|
||||
```typescript
|
||||
import {
|
||||
pgTable,
|
||||
bigint,
|
||||
text,
|
||||
timestamp,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
// Users table
|
||||
export const users = pgTable("users", {
|
||||
id: bigint({ mode: 'bigint' }).primaryKey().generatedAlwaysAsIdentity(),
|
||||
email: text("email").unique().notNull(),
|
||||
name: text("name").notNull(),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
});
|
||||
```
|
||||
|
||||
There are a couple key pieces here:
|
||||
|
||||
1. We import `bigint` so that we can declare a column of that type.
|
||||
2. We specify that it is a primary key with `.primaryKey()`.
|
||||
3. We declare its default value as `generated always as identity` via
|
||||
`.generatedAlwaysAsIdentity()`.
|
||||
|
||||
Note: you need to specify the `mode` for `bigint` or else you will see a
|
||||
`TypeError: Cannot read properties of undefined (reading 'mode')` error.
|
||||
|
||||
If we run `npx drizzle-kit generate` the SQL migration file that gets
|
||||
generated will contain something like this:
|
||||
|
||||
```sql
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1),
|
||||
"email" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "users_email_unique" UNIQUE("email")
|
||||
);
|
||||
```
|
||||
56
drizzle/get-fields-for-inserted-row.md
Normal file
56
drizzle/get-fields-for-inserted-row.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Get Fields For Inserted Row
|
||||
|
||||
With Drizzle, we can insert a row with a set of values like so:
|
||||
|
||||
```typescript
|
||||
await db
|
||||
.insert(todoItems)
|
||||
.values({
|
||||
title,
|
||||
userId,
|
||||
description,
|
||||
})
|
||||
```
|
||||
|
||||
The result of this is `QueryResult<never>`. In other words, nothing useful is
|
||||
coming back to us from the database.
|
||||
|
||||
Sometimes an insert is treated as a fire-and-forget (as long as it succeeds) or
|
||||
since we know what data we are inserting, we don't need the database to
|
||||
response. But what about values that are generated or computed by the database
|
||||
-- such as an id from a sequence, timestamp columns that default to `now()`, or
|
||||
generated columns.
|
||||
|
||||
To get all the fields of a freshly inserted row, we can tack on [the
|
||||
`returning()` function](https://orm.drizzle.team/docs/insert#insert-returning)
|
||||
(which likely adds something like [`returning
|
||||
*`](https://www.postgresql.org/docs/current/dml-returning.html)) to the insert
|
||||
query under the hood).
|
||||
|
||||
```typescript
|
||||
await db
|
||||
.insert(todoItems)
|
||||
.values({
|
||||
title,
|
||||
userId,
|
||||
description,
|
||||
})
|
||||
.returning()
|
||||
```
|
||||
|
||||
This will have a return type of `Array<type todoItems>` which means that for
|
||||
each inserted row we'll have all the fields (columns) for that row.
|
||||
|
||||
Alternatively, if we just need the generated ID for the new row(s), we can use
|
||||
a partial return like so:
|
||||
|
||||
```typescript
|
||||
await db
|
||||
.insert(todoItems)
|
||||
.values({
|
||||
title,
|
||||
userId,
|
||||
description,
|
||||
})
|
||||
.returning({ id: todoItems.id })
|
||||
```
|
||||
28
git/check-how-a-file-is-being-ignored.md
Normal file
28
git/check-how-a-file-is-being-ignored.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Check How A File Is Being Ignored
|
||||
|
||||
There are a few places on your machine where you can specify the files that git
|
||||
should ignore. The most common is a repository's `.gitignore` file. The other
|
||||
places those excludes are specified can be more obscure. Fortunately, `git
|
||||
check-ignore` is a command that can show you specifically where.
|
||||
|
||||
For instance, let's check why my `notes.md` file is being ignored.
|
||||
|
||||
```bash
|
||||
$ git check-ignore -v .DS_Store
|
||||
.git/info/exclude:7:notes.md notes.md
|
||||
```
|
||||
|
||||
At some point I added it to my repo's `.git/info/exclude` file. The `-v` flag
|
||||
(_verbose_) when included with `check-ignore` tells me the file location.
|
||||
|
||||
How about these pesky `.DS_Store` directories? How are those being ignored?
|
||||
|
||||
```bash
|
||||
$ git check-ignore -v .DS_Store
|
||||
/Users/jbranchaud/.gitignore:3:.DS_Store .DS_Store
|
||||
```
|
||||
|
||||
Ah yes, I had added it to my _global exclude file_ which I've configured in
|
||||
`~/.gitconfig` to be the `~/.gitignore` file.
|
||||
|
||||
See `man git-check-ignore` for more details.
|
||||
33
git/override-the-global-git-ignore-file.md
Normal file
33
git/override-the-global-git-ignore-file.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Override The Global Git Ignore File
|
||||
|
||||
One of the places that `git` looks when deciding whether to pay attention to or
|
||||
ignore a file is in your global _ignore_ file. By default, `git` will look for
|
||||
this file at `$XDG_CONFIG_HOME/git/ignore` or `$HOME/.config/git/ignore`.
|
||||
|
||||
I don't have `$XDG_CONFIG_HOME` set on my machine, so it will fall back to the
|
||||
config directory under `$HOME`.
|
||||
|
||||
I may have to create the `git` directory and `ignore` file.
|
||||
|
||||
```bash
|
||||
$ mkdir $HOME/.config/git
|
||||
$ touch $HOME/.config/git/ignore
|
||||
```
|
||||
|
||||
Then I can add file and directories to exclude to that `ignore` file just like
|
||||
I would any other `.gitignore` file.
|
||||
|
||||
If I'd prefer for the global _ignore_ file to live somewhere else, I can
|
||||
specify that location and filename in my `$HOME/.gitconfig` file.
|
||||
|
||||
```
|
||||
[core]
|
||||
excludesFile = ~/.gitignore
|
||||
```
|
||||
|
||||
Setting this will override the default, meaning the default file mentioned
|
||||
above will be ignored ("now you know how it feels, ignore file!"). In this
|
||||
case, I'll need to create the `.gitignore` file in my home directory and add
|
||||
any of my ignore rules.
|
||||
|
||||
[source](https://git-scm.com/docs/gitignore)
|
||||
@@ -15,4 +15,10 @@ $ godoc -http=:6060
|
||||
|
||||
and then visit `localhost:6060`.
|
||||
|
||||
Note: if you do not already have `godoc` installed, you can install it with:
|
||||
|
||||
```bash
|
||||
$ go install golang.org/x/tools/cmd/godoc@latest
|
||||
```
|
||||
|
||||
[source](http://www.andybritcliffe.com/post/44610795381/offline-go-lang-documentation)
|
||||
|
||||
21
internet/analyze-your-website-performance.md
Normal file
21
internet/analyze-your-website-performance.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Analyze Your Website Performance
|
||||
|
||||
The [PageSpeed Insights](https://pagespeed.web.dev/) tool from Google is a
|
||||
great way to quickly get actionable insights about where to improve your
|
||||
website and app's _Performance_, _Accessibility_, and _SEO_.
|
||||
|
||||
To see how your public site or app does, grab its URL and analyze it at
|
||||
[PageSpeed Insights](https://pagespeed.web.dev/).
|
||||
|
||||
It will take a minute to run on either Mobile or Desktop (make sure to check
|
||||
both) and then will output four headline numbers (out of 100) for each of the
|
||||
categories.
|
||||
|
||||
You can then dig in to each category to see what recommendations they make for
|
||||
improving your score.
|
||||
|
||||
This can also be run directly from Chrome devtools which is useful if you want
|
||||
to see how a locally running site is doing. You can run the analysis from the
|
||||
_Lighthouse_ tab of devtools. Note: if the _Performance_ score looks bad, it
|
||||
might be that you are running a non-optimized dev server that isn't reflective
|
||||
of how your site would do in production.
|
||||
14
internet/exclude-ai-overview-from-google-search.md
Normal file
14
internet/exclude-ai-overview-from-google-search.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Exclude AI Overview From Google Search
|
||||
|
||||
At the top of most Google searches these days is a section of text that takes a
|
||||
moment to appear, presumably because it is being generated in the moment. This
|
||||
is Google's _AI Overview_. These are sometimes useful summaries of the article
|
||||
you are about to click on anyway. Other times the overview is no good, it takes
|
||||
up a bunch of screen real estate, and may even [10x the energy consumed by a
|
||||
regular
|
||||
search](https://www.reddit.com/r/technology/comments/1dsvefb/googles_ai_search_summaries_use_10x_more_energy/).
|
||||
|
||||
If you want to exclude the _AI Overview_, tack on a `-ai` when writing out your
|
||||
search query.
|
||||
|
||||
[source](https://www.yahoo.com/tech/turn-off-ai-overview-results-170014202.html)
|
||||
55
java/ensure-resources-always-get-closed.md
Normal file
55
java/ensure-resources-always-get-closed.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Ensure Resources Always Get Closed
|
||||
|
||||
Java has a construct known as _try-with-resource_ that allows us to always
|
||||
ensure opened resources get closed. This is safer than similar cleanup in the
|
||||
`finally` block which could still leave a memory leak if an error occurs in
|
||||
that block.
|
||||
|
||||
To use the _try-with-resource_ construct, instantiate your opened resource in
|
||||
parentheses with the `try`.
|
||||
|
||||
```java
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The resource will be automatically closed when the try/catch block completes.
|
||||
|
||||
Here is a full example:
|
||||
|
||||
```java
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
|
||||
public class FileReaderExample {
|
||||
public static void main(String[] args) {
|
||||
String fileName = "example.txt";
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
|
||||
String line;
|
||||
int lineCount = 0;
|
||||
|
||||
while ((line = reader.readLine()) != null && lineCount < 5) {
|
||||
System.out.println(line);
|
||||
lineCount++;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.out.println("An error occurred while reading the file: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can even specify multiple resources in one `try`. The above does that, but
|
||||
this will make it more obvious:
|
||||
|
||||
```java
|
||||
try (FileReader fr = new FileReader(filename);
|
||||
BufferedReader br = new BufferedReader(fr)) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
[source](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html)
|
||||
28
javascript/check-media-queries-from-javascript.md
Normal file
28
javascript/check-media-queries-from-javascript.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Check Media Queries From JavaScript
|
||||
|
||||
I'm usually thinking about and [using media
|
||||
queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries)
|
||||
from a CSS context. I use them to control what styles are displayed for a
|
||||
variety of scenarios, such as at different screen widths, when a user prefers
|
||||
reduced motion, or when the user prefers a dark color scheme.
|
||||
|
||||
The current value of various media queries can be checked from a JavaScript
|
||||
context as well.
|
||||
|
||||
For instance, if we want to see if the user prefers a _dark_ color schema, we
|
||||
can look for a _match_ on that media query with
|
||||
[`matchMedia`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia).
|
||||
|
||||
```javascript
|
||||
> window.matchMedia('(prefers-color-scheme: dark)')
|
||||
MediaQueryList {media: '(prefers-color-scheme: dark)', matches: true, onchange: null}
|
||||
> window.matchMedia('(prefers-color-scheme: dark)')['matches']
|
||||
true
|
||||
```
|
||||
|
||||
This queries for the [`prefers-color-scheme` media
|
||||
feature](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme).
|
||||
|
||||
The [Astro.build Blog
|
||||
Tutorial](https://docs.astro.build/en/tutorial/6-islands/2/#add-client-side-interactivity)
|
||||
shows an example of using this to wire up a Light/Dark mode toggle.
|
||||
61
javascript/make-truly-deep-clone-with-structured-clone.md
Normal file
61
javascript/make-truly-deep-clone-with-structured-clone.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Make Truly Deep Clone With Structured Clone
|
||||
|
||||
There are a lot of ways to make a copy of an object. They are all hacks and
|
||||
they all fail in certain circumstances. Using the spread trick only gives you a
|
||||
shallow copy where references to nested objects and arrays can still be
|
||||
updated. The `JSON.stringify` trick has to make things like dates into strings,
|
||||
so it is lossy.
|
||||
|
||||
There is however now a dedicated method for deep copies with broad support
|
||||
called
|
||||
[`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone).
|
||||
It is available on `window`. Let's take a look at it and see how it comparse to
|
||||
the spread operator trick.
|
||||
|
||||
```javascript
|
||||
> // some data setup
|
||||
|
||||
> const data = { one: 1, two: 2, rest: [3,4,5] }
|
||||
|
||||
> const obj = { hello: 'world', taco: 'bell', data }
|
||||
|
||||
> const shallowObj = { ...obj }
|
||||
|
||||
> const deepObj = structuredClone(obj)
|
||||
|
||||
> // let's modify the original `data.rest` array
|
||||
|
||||
> data.rest.push(6)
|
||||
4
|
||||
> data
|
||||
{ one: 1, two: 2, rest: [ 3, 4, 5, 6 ] }
|
||||
|
||||
> // now let's see who was impacted by that mutation
|
||||
|
||||
> obj
|
||||
{
|
||||
hello: 'world',
|
||||
taco: 'bell',
|
||||
data: { one: 1, two: 2, rest: [ 3, 4, 5, 6 ] }
|
||||
}
|
||||
|
||||
> shallowObj
|
||||
{
|
||||
hello: 'world',
|
||||
taco: 'bell',
|
||||
data: { one: 1, two: 2, rest: [ 3, 4, 5, 6 ] }
|
||||
}
|
||||
|
||||
> deepObj
|
||||
{
|
||||
hello: 'world',
|
||||
taco: 'bell',
|
||||
data: { one: 1, two: 2, rest: [ 3, 4, 5 ] }
|
||||
}
|
||||
```
|
||||
|
||||
The `shallowObj` from the spread operator copy was mutated even though we
|
||||
didn't intend for that. The `deepObj` from `structuredClone` was a true deep
|
||||
copy and was unaffected.
|
||||
|
||||
[source](https://www.builder.io/blog/structured-clone)
|
||||
24
mac/keyboard-shortcuts-for-interacting-with-text-areas.md
Normal file
24
mac/keyboard-shortcuts-for-interacting-with-text-areas.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Keyboard Shortcuts For Interacting With Text Areas
|
||||
|
||||
When interacting with a document text area on MacOS (such as in the Notes app),
|
||||
there are a bunch of keyboard shortcuts made available to you via the operating
|
||||
system.
|
||||
|
||||
A couple common ones that I'm used to from Unix environments are:
|
||||
|
||||
- `ctrl-a` to move the cursor to the beginning of the line
|
||||
- `ctrl-e` to move the cursor to the end of the line
|
||||
- `ctrl-p` to move the cursor up a line (this is a common _previous_ keybinding)
|
||||
- `ctrl-n` to move the cursor down a line (this is a common _next_ keybinding)
|
||||
|
||||
A handy one that I wasn't aware of is `ctrl-l` which will scroll the text area
|
||||
so that the cursor gets centered in the view area.
|
||||
|
||||
A much more niche one I wasn't aware of is `ctrl-t` which swaps the character
|
||||
before the cursor with the character after the cursor. I can only imagine this
|
||||
being useful for quickly fixing transposed characters in a misspelled word,
|
||||
e.g. `baer`.
|
||||
|
||||
There are many more *Document Shortcuts* as well as well as keyboard shorcuts
|
||||
for other apps and situations. The full listing is at [Mac Keyboard
|
||||
Shortcuts](https://support.apple.com/en-us/102650).
|
||||
25
postgres/add-unique-constraint-using-existing-index.md
Normal file
25
postgres/add-unique-constraint-using-existing-index.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Add Unique Constraint Using Existing Index
|
||||
|
||||
Adding a unique constraint to an existing column on a production table can
|
||||
block updates. If we need to avoid this kind of locking for the duration of
|
||||
index creation, then we can first create the index concurrently and then use
|
||||
that existing index to back the unique constraint.
|
||||
|
||||
```sql
|
||||
create index concurrently users_email_idx on users (email);
|
||||
|
||||
-- wait for that to complete
|
||||
|
||||
alter table users
|
||||
add constraint unique_users_email unique using index users_email_idx;
|
||||
```
|
||||
|
||||
First, we concurrently create the index. The time this takes will depend on how
|
||||
large the table is. That's the blocking time we are avoiding with this
|
||||
approach. Then once that completes we can apply a unique constraint using that
|
||||
preexisting index.
|
||||
|
||||
Note: if a non-unique value exists in the table for that column, adding the
|
||||
constraint will fail. You'll need to deal with that _duplicate_ value first.
|
||||
|
||||
[source](https://dba.stackexchange.com/questions/81627/postgresql-9-3-add-unique-constraint-using-an-existing-unique-index)
|
||||
53
postgres/concatenate-strings-with-a-separator.md
Normal file
53
postgres/concatenate-strings-with-a-separator.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Concatenate Strings With A Separator
|
||||
|
||||
I was putting together an example of using a generated column that concatenates
|
||||
string values from a few other columns. I used manual concatenation with the
|
||||
`||` operator like so:
|
||||
|
||||
```sql
|
||||
create table folders (
|
||||
id integer generated always as identity primary key,
|
||||
user_id integer not null,
|
||||
name text not null,
|
||||
parent_folder_id integer references folders(id),
|
||||
path text generated always as (
|
||||
user_id::text || ':' || lower(name) || ':' || coalesce(parent_folder_id::text, '0')
|
||||
) stored
|
||||
);
|
||||
```
|
||||
|
||||
Instead of doing that manual concatenation for the `path` generated column, I
|
||||
can use
|
||||
[`concat_ws`](https://www.postgresql.org/docs/current/functions-string.html).
|
||||
|
||||
```sql
|
||||
create table folders (
|
||||
id integer generated always as identity primary key,
|
||||
user_id integer not null,
|
||||
name text not null,
|
||||
parent_folder_id integer references folders(id),
|
||||
path text generated always as (
|
||||
concat_ws(
|
||||
':',
|
||||
user_id::text,
|
||||
lower(name),
|
||||
coalesce(parent_folder_id::text, '0')
|
||||
)
|
||||
) stored
|
||||
);
|
||||
```
|
||||
|
||||
The first argument to `concat_ws` is the separator I want to use. The remaining
|
||||
arguments are the strings that should be concatenated with that separator.
|
||||
|
||||
One other things that is nice about `concat_ws` is that it will ignore `null`
|
||||
values that it receives.
|
||||
|
||||
```sql
|
||||
> select concat_ws(':', 'one', 'two', null, 'three');
|
||||
+---------------+
|
||||
| concat_ws |
|
||||
|---------------|
|
||||
| one:two:three |
|
||||
+---------------+
|
||||
```
|
||||
35
postgres/enforce-uniqueness-on-column-expression.md
Normal file
35
postgres/enforce-uniqueness-on-column-expression.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Enforce Uniqueness On Column Expression
|
||||
|
||||
When creating a table for, say `users`, where you will store `email` addresses,
|
||||
you'll likely want to enforce uniqueness on the that `email` field. And because
|
||||
people have all sorts of ways of entering their emails, in terms of casing, you
|
||||
may be tempted to try to enforce uniqueness on a lowercased version of `email`.
|
||||
|
||||
```sql
|
||||
create table users (
|
||||
id integer generated always as identity primary key,
|
||||
email text not null,
|
||||
unique ( lower(email) ) -- !! this won't work
|
||||
);
|
||||
```
|
||||
|
||||
A unique _constraint_ must be on one or more columns. You cannot include an
|
||||
_expression_ when defining the unique constraint.
|
||||
|
||||
You can however accomplish similar aims with [a _unique index_ on the
|
||||
expression](https://www.postgresql.org/docs/current/indexes-expressional.html).
|
||||
That is because the index is able to store the result of the expression in
|
||||
itself.
|
||||
|
||||
```sql
|
||||
create table users (
|
||||
id integer generated always as identity primary key,
|
||||
email text not null
|
||||
);
|
||||
|
||||
create unique index unq_lower_email on users ( lower(email) );
|
||||
```
|
||||
|
||||
This is likely what you want for this example anyway because it will probably
|
||||
be a common query to look up the user by `lower(email)` and the index will
|
||||
speed up those queries.
|
||||
30
postgres/postgres-does-not-support-unsigned-integers.md
Normal file
30
postgres/postgres-does-not-support-unsigned-integers.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Postgres Does Not Support Unsigned Integers
|
||||
|
||||
PostgreSQL has a variety of sizes of integer types, from `smallint` (2 bytes)
|
||||
to `integer` (4 bytes) to `bigint` (8 bytes), as well as [other numeric
|
||||
types](https://www.postgresql.org/docs/current/datatype-numeric.html).
|
||||
|
||||
It does _not_ however support unsigned versions of these numeric types.
|
||||
|
||||
That means, with an `integer` for instance, we can store numbers between
|
||||
`-2147483648` and `+2147483647`. That's everything that can fit into 4 bytes.
|
||||
In a system that supported 4 byte unsigned integers we'd be able to represent
|
||||
from `0` all the way up to `4294967295`.
|
||||
|
||||
In PostgreSQL, we're limited to these _signed_ numeric types.
|
||||
|
||||
That means if we were hoping that the data type could essentially enforce a
|
||||
non-negative restriction on the data in one of our columns, we're going to have
|
||||
to be more creative. The obvious choice to me is to consider adding a [check
|
||||
constraint](https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-CHECK-CONSTRAINTS)
|
||||
(e.g. `quantity integer check (quantity > 0)`).
|
||||
|
||||
Another option, as pointed out by [this StackOverflow
|
||||
answer](https://stackoverflow.com/a/31833279/535590), is to create [a
|
||||
user-defined _domain
|
||||
type_](https://www.postgresql.org/docs/current/domains.html) that restricts
|
||||
valid values. To me, the ergonomics of using a domain type are a bit awkward
|
||||
and not worth the effort.
|
||||
|
||||
With either of these solutions, we are only approximating an unsigned integer
|
||||
and do not actually have the same range of values available.
|
||||
46
postgres/put-unique-constraint-on-generated-column.md
Normal file
46
postgres/put-unique-constraint-on-generated-column.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Put Unique Constraint On Generated Column
|
||||
|
||||
You cannot apply a _unique constraint_ to an expression over a column, e.g.
|
||||
`lower(email)`. You can, however, create a [generated
|
||||
column](https://www.postgresql.org/docs/current/ddl-generated-columns.html) for
|
||||
that expression and then apply the unique constraint to that generated column.
|
||||
|
||||
Here is what that could look like:
|
||||
|
||||
```sql
|
||||
> create table users (
|
||||
id integer generated always as identity primary key,
|
||||
name text not null,
|
||||
email text not null,
|
||||
email_lower text generated always as (lower(email)) stored,
|
||||
unique ( email_lower )
|
||||
);
|
||||
|
||||
> \d users
|
||||
+-------------+---------+-----------------------------------------------------------------+
|
||||
| Column | Type | Modifiers |
|
||||
|-------------+---------+-----------------------------------------------------------------|
|
||||
| id | integer | not null generated always as identity |
|
||||
| name | text | not null |
|
||||
| email | text | not null |
|
||||
| email_lower | text | default lower(email) generated always as (lower(email)) stored |
|
||||
+-------------+---------+-----------------------------------------------------------------+
|
||||
Indexes:
|
||||
"users_pkey" PRIMARY KEY, btree (id)
|
||||
"users_email_lower_key" UNIQUE CONSTRAINT, btree (email_lower)
|
||||
```
|
||||
|
||||
And then an demonstration of violating that constraint:
|
||||
|
||||
```sql
|
||||
|
||||
> insert into users (name, email) values ('Bob', 'bob@email.com');
|
||||
INSERT 0 1
|
||||
|
||||
> insert into users (name, email) values ('Bobby', 'BOB@email.com');
|
||||
duplicate key value violates unique constraint "users_email_lower_key"
|
||||
DETAIL: Key (email_lower)=(bob@email.com) already exists.
|
||||
```
|
||||
|
||||
The main tradeoff here is that you are doubling the amount of storage you need
|
||||
for that column. Unless it is a massive table, that is likely not an issue.
|
||||
37
rails/define-the-root-path-for-the-app.md
Normal file
37
rails/define-the-root-path-for-the-app.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Define The Root Path For The App
|
||||
|
||||
The `root_path` helper that you might want to use in Rails controllers and
|
||||
views is not available by default.
|
||||
|
||||
```ruby
|
||||
> Rails.application.routes.url_helpers.root_path
|
||||
|
||||
ruby/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.14.0/lib/irb.rb:1285:in `full_message': undefined method `root_path' for #<Module:0x0000000106d11738> (NoMethodError)
|
||||
|
||||
Rails.application.routes.url_helpers.root_path
|
||||
^^^^^^^^^^
|
||||
Did you mean? logout_path
|
||||
book_path
|
||||
```
|
||||
|
||||
It needs to be declared in the `config/routes.rb` file with the controller
|
||||
action that it points to.
|
||||
|
||||
```ruby
|
||||
# config/routes.rb
|
||||
Rails.application.routes.draw do
|
||||
root 'home#index'
|
||||
end
|
||||
```
|
||||
|
||||
Once this is defined the `root_path` will now be available with the rest of
|
||||
your URL helpers.
|
||||
|
||||
```ruby
|
||||
better-reads(dev)> reload!
|
||||
Reloading...
|
||||
better-reads(dev)> Rails.application.routes.url_helpers.root_path
|
||||
=> "/"
|
||||
```
|
||||
|
||||
[source](https://guides.rubyonrails.org/routing.html#using-root)
|
||||
25
rails/rollback-a-couple-migrations.md
Normal file
25
rails/rollback-a-couple-migrations.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Rollback A Couple Migrations
|
||||
|
||||
Let's say we need to rollback a couple Rails migrations that have been applied
|
||||
to our local environment. We run `rails db:migrate:status` and see that there
|
||||
are _2_ migrations that we want to _undo_.
|
||||
|
||||
We can accomplish this by using the `STEP` env var with the rollback command.
|
||||
|
||||
```bash
|
||||
$ rails db:rollback STEP=2
|
||||
```
|
||||
|
||||
Just set `STEP` to the number of migrations that we need to rollback. If we
|
||||
then rerun `rails db:migrate:status` we'll now see those latest two migrations
|
||||
are `down`.
|
||||
|
||||
Note: by default Rails doesn't like to operate with pending migrations. If we
|
||||
want to temporarily disable the pending migration check, we can alter the
|
||||
migration error config in `config/development.rb`.
|
||||
|
||||
```diff
|
||||
# Raise an error on page load if there are pending migrations.
|
||||
- # config.active_record.migration_error = :page_load
|
||||
+ config.active_record.migration_error = false
|
||||
```
|
||||
45
rails/set-datetime-to-include-time-zone-in-migrations.md
Normal file
45
rails/set-datetime-to-include-time-zone-in-migrations.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Set Datetime To Include Time Zone In Migrations
|
||||
|
||||
When using Rails and PostgreSQL, your migrations will contain DSL syntax like
|
||||
`t.datetime` and `t.timestamps` which will produce columns using the
|
||||
`timestamp` (`without time zone`) Postgres data type.
|
||||
|
||||
While reading [A Simple Explanation of Postgres' <code>Timestamp with Time
|
||||
Zone</code>](https://naildrivin5.com/blog/2024/10/10/a-simple-explanation-of-postgres-timestamp-with-time-zone.html),
|
||||
I learned that there is a way to configure your app to instead use
|
||||
`timestamptz` by default. This data type is widely recommended as a good
|
||||
default, so it is nice that we can configure Rails to use it.
|
||||
|
||||
First, add these lines to a new initializer (`config/initializers/postgres.rb`)
|
||||
file.
|
||||
|
||||
```ruby
|
||||
require "active_record/connection_adapters/postgresql_adapter"
|
||||
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz
|
||||
```
|
||||
|
||||
Alternatively, you can configure this via `config/application.rb` per the
|
||||
[Configuring ActiveRecord
|
||||
docs](https://guides.rubyonrails.org/configuring.html#activerecord-connectionadapters-postgresqladapter-datetime-type).
|
||||
|
||||
Then, if you have a new migration like the following:
|
||||
|
||||
```ruby
|
||||
class AddEventsTable < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :events do |t|
|
||||
t.string :title
|
||||
t.text :description
|
||||
t.datetime :start_time
|
||||
t.datetime :end_time
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
you can expect to have four `timestamptz` columns, namely `start_time`,
|
||||
`end_time`, `created_at`, and `updated_at`.
|
||||
|
||||
Here is the [Rails PR](https://github.com/rails/rails/pull/41084) that adds
|
||||
this config option.
|
||||
33
rails/temporarily-turn-off-pending-migrations-error.md
Normal file
33
rails/temporarily-turn-off-pending-migrations-error.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Temporarily Turn Off Pending Migrations Error
|
||||
|
||||
Whenever I'm working on an end-to-end feature in a Rails app, soon or later I
|
||||
am going to see the _Pending Migrations_ error page. I try to visit one of the
|
||||
routes in the browser and the Rails app serves this error page instead of my
|
||||
actual request response.
|
||||
|
||||
This is typically what I want. If there are migrations just sitting there that
|
||||
haven't been run yet, that's probably an issue. Maybe I just pulled down the
|
||||
latest changes from my teammates. The app isn't going to work properly without
|
||||
whatever schema changes are prescribed in those pending migrations.
|
||||
|
||||
The thing to do is run those migrations.
|
||||
|
||||
In some special cases though, I know what I'm doing and I would like to operate
|
||||
my app locally with specific migrations not yet applied.
|
||||
|
||||
To skip the error, I can change this `config/environments/development.rb`
|
||||
setting from:
|
||||
|
||||
```ruby
|
||||
config.active_record.migration_error = :page_load
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```ruby
|
||||
config.active_record.migration_error = false
|
||||
```
|
||||
|
||||
I just need to make sure to switch it back when I'm done.
|
||||
|
||||
[source](https://til.hashrocket.com/posts/ujcixh5rwi-rails-ignore-pending-migrations)
|
||||
45
ruby/prevent-erb-lint-from-removing-opening-tags.md
Normal file
45
ruby/prevent-erb-lint-from-removing-opening-tags.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Prevent erb_lint From Removing Opening Tags
|
||||
|
||||
The [`erb_lint` gem](https://github.com/Shopify/erb_lint) is a tool from
|
||||
shopify for linting and auto-formatting ERB files. When I first set it up in a
|
||||
Rails codebase with the base `.erb-lint.yml` recommended in the README, I ran
|
||||
into a pernicious issue. The linter wanted to remove opening tags (i.e. `<%`
|
||||
and `<%=`) from my ERB files.
|
||||
|
||||
So, for a file that looked like this:
|
||||
|
||||
```erb
|
||||
<div>
|
||||
<%= form_with(url: login_path, scope: :session) do |f| %>
|
||||
<div>
|
||||
<%= f.label :email %>
|
||||
<%= f.email_field :email %>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
It would get formatted to this:
|
||||
|
||||
```erb
|
||||
<div>
|
||||
form_with(url: login_path, scope: :session) do |f| %>
|
||||
<div>
|
||||
f.label :email %>
|
||||
f.email_field :email %>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
Yikes!
|
||||
|
||||
I had to disable a couple rules (under `rubocop_config:`) in the `.erb-lint.yml` file to get it to stop
|
||||
doing this.
|
||||
|
||||
```yaml
|
||||
Layout/InitialIndentation:
|
||||
Enabled: false
|
||||
Layout/TrailingEmptyLines:
|
||||
Enabled: false
|
||||
```
|
||||
|
||||
[source](https://github.com/Shopify/erb_lint/issues/222)
|
||||
20
unix/convert-jpeg-to-png-with-ffmpeg.md
Normal file
20
unix/convert-jpeg-to-png-with-ffmpeg.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Convert JPEG To PNG With ffmpeg
|
||||
|
||||
The `ffmpeg` utility "is a universal media converter." That means we can use it
|
||||
to convert, for instance, a JPEG file to a PNG file.
|
||||
|
||||
There is not a lot to a conversion like this. We use `-i` to specify the
|
||||
existing input file (a JPEG) and then the other argument is the name and
|
||||
extension of the output file.
|
||||
|
||||
```bash
|
||||
$ ls
|
||||
profile.jpg
|
||||
|
||||
$ ffmpeg -i profile.jpg profile.png
|
||||
|
||||
$ ls
|
||||
profile.jpg profile.png
|
||||
```
|
||||
|
||||
See `man ffmpeg` for more details.
|
||||
32
unix/make-direnv-less-noisy.md
Normal file
32
unix/make-direnv-less-noisy.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Make Direnv Less Noisy
|
||||
|
||||
I've been using [`direnv`](https://direnv.net/) to manage project and folder
|
||||
specific environment variables for a bit now. I've found it to be pretty
|
||||
seamless. It can feel like it is littering my shell with too much output when I
|
||||
change directories though.
|
||||
|
||||
There are two levers to control its output.
|
||||
|
||||
First, the direnv logs (e.g. `direnv: loading ~/.../.envrc`) can be controlled
|
||||
with the `DIRENV_LOG_FORMAT` env var. Add this to the
|
||||
`~/.config/direnv/direnvrc` file (add that directory and file if necessary).
|
||||
You can leave it blank to altogether hide log messages or you can gray-out the
|
||||
log messages like this:
|
||||
|
||||
```
|
||||
export DIRENV_LOG_FORMAT=$'\033[2mdirenv: %s\033[0m'
|
||||
```
|
||||
|
||||
Second, you can hide the env var diff with a separate config. This diff is not
|
||||
covered under the umbrella of logs controlled by the above setting. Set
|
||||
[`hide_env_diff` in the `~/.config/direnv/direnv.toml`
|
||||
file](https://direnv.net/man/direnv.toml.1.html#codehideenvdiffcode):
|
||||
|
||||
```toml
|
||||
[global]
|
||||
hide_env_diff = true
|
||||
```
|
||||
|
||||
This second config was only added as of `v2.34.0`.
|
||||
|
||||
[source](https://esham.io/2023/10/direnv)
|
||||
17
vim/switch-moving-end-of-visual-selection.md
Normal file
17
vim/switch-moving-end-of-visual-selection.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Switch Moving End Of Visual Selection
|
||||
|
||||
When I go into character-wise visual selection mode, one end of the visual
|
||||
selection is fixed while I move my cursor around to define the other end of it.
|
||||
|
||||
Let's say I've arranged a visual selection that encompasses several lines of my
|
||||
file. And then I realize that the fixed front-end of my visual selection is off
|
||||
by a bit. Maybe I've selected an entire function definition and I just want to
|
||||
inner part of the function.
|
||||
|
||||
Instead of starting over with my visual selection. I can leave the right-end of
|
||||
the visual selection where it is, hit `o` which will switch the moving end to
|
||||
the other side, and then continue making adjustments from there.
|
||||
|
||||
I can always hit `o` again to switch it back to the original side.
|
||||
|
||||
See `:h v_o` for more details.
|
||||
18
vscode/jump-to-problems-in-the-current-file.md
Normal file
18
vscode/jump-to-problems-in-the-current-file.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Jump To Problems In The Current File
|
||||
|
||||
VSCode has a system for different extensions to report problems. The editor
|
||||
will visually flag those problems in the editor with a red squiggly underline.
|
||||
It will also list them in the _Problems_ tab of the bottom tray (hit `Cmd+j` to
|
||||
pop that tray open).
|
||||
|
||||
If there are some active problems in your current file, you can jump right to
|
||||
them.
|
||||
|
||||
Hit `F8` and you'll jump to the next problem after wherever the cursor is. Hit
|
||||
`F8` again and it will go to the next one.
|
||||
|
||||
Want to track back through the file? Hit `Shift+F8` and you'll jump to the
|
||||
closest problem _behind_ the current cursor position.
|
||||
|
||||
Using these two, you can quickly survey the current problems in your file
|
||||
before deciding how to proceed.
|
||||
19
workflow/add-hotkeys-for-specific-raycast-extensions.md
Normal file
19
workflow/add-hotkeys-for-specific-raycast-extensions.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Add Hotkeys For Specific Raycast Extensions
|
||||
|
||||
One of the main things I use [Raycast](https://www.raycast.com/) for is its
|
||||
built-in clipboard history extension. It's super handy when I've copied a Git
|
||||
SHA or a URL or a bit of text several copies ago and I want to be able to refer
|
||||
back to it. It keeps 7 days of clipboard history by default, so anything that I
|
||||
remember copying is going to be there.
|
||||
|
||||
To get there, I have to trigger Raycast, which I usually open via Alfred (I
|
||||
know, 😅). Then I search for Clipboard History (or find it under recent
|
||||
suggestions).
|
||||
|
||||
Raycast supports arbitrary hotkey bindings for triggering different extensions
|
||||
and their features. So I can get to the Clipboard History in a single step --
|
||||
e.g. with `Ctrl+Shift+V`.
|
||||
|
||||
From Raycast _Settings_, go to the _Extensions_ tab. Find _Clipboard History_
|
||||
in the list and then click the _Record Hotkey_ input for it. I then type
|
||||
`Ctrl+Shift+V` and I'm all set.
|
||||
64
workflow/add-subscriber-to-kit-form-via-api.md
Normal file
64
workflow/add-subscriber-to-kit-form-via-api.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Add Subscriber To Kit Form Via API
|
||||
|
||||
Because the Kit API is a bit sparse in words, I found it difficult to figure
|
||||
out exactly what I needed to do to add a subscriber via a custom form. After
|
||||
some experimenting and reading through [Course
|
||||
Builder](https://github.com/badass-courses/course-builder/blob/main/packages/core/src/providers/convertkit.ts)
|
||||
code, I was able to figure out that with the Kit v4 API there are two separate
|
||||
calls that need to be made.
|
||||
|
||||
First, you need to create an `inactive` subscriber with the user's email (and
|
||||
first name if given) via the [Create a subscriber
|
||||
endpoint](https://developers.kit.com/v4#create-a-subscriber).
|
||||
|
||||
```typescript
|
||||
async function createSubscriber(
|
||||
data: SubscriberData,
|
||||
apiKey: string,
|
||||
): Promise<Response> {
|
||||
return fetch('https://api.convertkit.com/v4/subscribers', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Kit-Api-Key': apiKey,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email_address: data.email,
|
||||
first_name: data.name,
|
||||
state: 'inactive',
|
||||
}),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Since this subscriber is `inactive`, they won't show up in the Kit dashboard,
|
||||
but a subscriber record of them has been created.
|
||||
|
||||
Second, you need to add the subscriber to something, like a form or a sequence.
|
||||
In my case, I had already created a form via the Kit dashboard, so I grab that
|
||||
_Form ID_ and stick it in my `.env`. That way I am able to [add the subscriber
|
||||
to the form by email
|
||||
address](https://developers.kit.com/v4#add-subscriber-to-form-by-email-address).
|
||||
|
||||
```typescript
|
||||
async function addSubscriberToForm(
|
||||
email: string,
|
||||
apiKey: string,
|
||||
formId: string,
|
||||
): Promise<Response> {
|
||||
const url = `https://api.convertkit.com/v4/forms/${formId}/subscribers`
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Kit-Api-Key': apiKey,
|
||||
},
|
||||
body: JSON.stringify({ email_address: email }),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
This will send a confirmation email to the email address. Once the person has
|
||||
opened the email and hit 'Confirm', their corresponding `subscriber` record
|
||||
will be marked as `active` and they will be added to the list of subscribers
|
||||
for that form.
|
||||
23
workflow/configure-email-redirect-with-cloudflare.md
Normal file
23
workflow/configure-email-redirect-with-cloudflare.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Configure Email Redirect With Cloudflare
|
||||
|
||||
I have a domain registered with Cloudflare --
|
||||
[visualmode.dev](https://www.visualmode.dev). I want to be able to sign up for
|
||||
services and receive emails as `josh@visualmode.dev`. I don't want a separate
|
||||
inbox that I have to check though.
|
||||
|
||||
The solution, for me, is to have Cloudflare redirect incoming emails to an
|
||||
email address/inbox that I'm already regularly checking.
|
||||
|
||||
On the _Email_ dashboard there is a _Routing Rules_ tab. In the _Custom
|
||||
Addresses_ section, I can click _Create Address_. There I specify the custom
|
||||
address (what will appear before the `@`), the action to make ("Send to an
|
||||
email"), and the destination (the existing email address for an inbox I
|
||||
regularly check).
|
||||
|
||||
Finally, I hit _Save_. If they don't already exist, I'll be prompted to confirm
|
||||
the setup of MX records for the domain. After confirming that, I should be able
|
||||
to receive emails via that new address.
|
||||
|
||||
The Email Routing dashboard will even show me a summary of all rerouted emails.
|
||||
|
||||
[source](https://blog.cloudflare.com/introducing-email-routing/#cloudflare-email-routing)
|
||||
@@ -6,7 +6,7 @@ convert it using the `ebook-convert` binary from `Calibre`.
|
||||
First, install `Calibre`:
|
||||
|
||||
```bash
|
||||
$ brew cask install calibre
|
||||
$ brew install --cask calibre
|
||||
```
|
||||
|
||||
Then convert your ePub using `ebook-convert`:
|
||||
|
||||
27
workflow/get-url-for-github-user-profile-photo.md
Normal file
27
workflow/get-url-for-github-user-profile-photo.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Get URL For GitHub User Profile Photo
|
||||
|
||||
You can access your (or really any user's) profile photo by visiting the GitHub
|
||||
profile URL with `.png` added on to the end.
|
||||
|
||||
So, my GitHub profile URL is:
|
||||
|
||||
```
|
||||
https://github.com/jbranchaud
|
||||
```
|
||||
|
||||
If I were to add `.png` on to that and visit it in my browser:
|
||||
|
||||
```
|
||||
https://github.com/jbranchaud.png
|
||||
```
|
||||
|
||||
I'd be redirected to the following URL:
|
||||
|
||||
```
|
||||
https://avatars.githubusercontent.com/u/694063?v=4
|
||||
```
|
||||
|
||||
This is the stable URL for my GitHub avatar. In the browser I will see the full
|
||||
resolution image which I can download as needed.
|
||||
|
||||
[source](https://dev.to/10xlearner/how-to-get-the-profile-picture-of-a-github-account-1d82)
|
||||
64
workflow/send-a-message-to-a-discord-channel.md
Normal file
64
workflow/send-a-message-to-a-discord-channel.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Send A Message To A Discord Channel
|
||||
|
||||
I recently added a form to [visualmode.dev](https://www.visualmode.dev) that
|
||||
when submitted should send the details to an internal channel in my discord
|
||||
server.
|
||||
|
||||
I didn't need to set up an _App_ or a _Bot_ to do this. It is much simpler than
|
||||
that. All I needed was a valid [_webhook_
|
||||
endpoint](https://discord.com/developers/docs/resources/webhook) for my channel
|
||||
that I can `POST` to.
|
||||
|
||||
From Discord, I can select _Edit Channel_ for a specific channel, go to the
|
||||
_Integrations_ tab, go to _Webhooks_, and then create a _New Webhook_. I can
|
||||
name it, save it, and then copy the webhook URL.
|
||||
|
||||
As a demonstration, I can `POST` to that webhook URL using `cURL` like so:
|
||||
|
||||
```bash
|
||||
curl -H "Content-Type: application/json" -X POST -d '{"content":"Hello from cURL!"}' <YOUR_WEBHOOK_URL>
|
||||
```
|
||||
|
||||
Similarly, in some non-public-facing code like a Next.js serverless function, I
|
||||
can `POST` to that webhook URL with the `content` that I want to appear in my
|
||||
channel.
|
||||
|
||||
```
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const data = await request.json()
|
||||
|
||||
const discordWebhookUrl = process.env.DISCORD_WEBHOOK_URL
|
||||
if (discordWebhookUrl) {
|
||||
try {
|
||||
const response = await fetch(discordWebhookUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: `New contact form submission:\nName: ${data.name}\nEmail: ${data.email}\nCompany: ${data.company}\nPhone: ${data.phone}\nMessage: ${data.message}`,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to send Discord message')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error sending to Discord:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to process form submission' },
|
||||
{ status: 500 },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ message: 'Form submitted successfully' })
|
||||
}
|
||||
```
|
||||
|
||||
This [Structure of Webhook
|
||||
guide](https://birdie0.github.io/discord-webhooks-guide/discord_webhook.html)
|
||||
has more details on how to specifically structure and format a more complex
|
||||
message.
|
||||
37
zsh/use-a-space-to-exclude-command-from-history.md
Normal file
37
zsh/use-a-space-to-exclude-command-from-history.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Use A Space To Exclude Command From History
|
||||
|
||||
When using a shell like `zsh`, you get the benefit of it keeping track of the
|
||||
history of the commands you've entered into the shell. This means you can
|
||||
quickly traverse pack to a previous command that you want to run again. It also
|
||||
means [a tool like `fzf` can hook into your history
|
||||
file](https://github.com/junegunn/fzf?tab=readme-ov-file#key-bindings-for-command-line)
|
||||
so that you can fuzzy-search for a command you may have executed weeks ago.
|
||||
|
||||
The history is stored on your machine in a plaintext file. Not every command
|
||||
should be stored in a plaintext file. For instance, you don't want `zsh` to
|
||||
persist a command that includes a password.
|
||||
|
||||
With the `histignorespace` option enabled in `zsh`, we can put a leading space
|
||||
in front of our command and it will be excluded from the history file.
|
||||
|
||||
Try it yourself:
|
||||
|
||||
```bash
|
||||
$ echo 'this command will be remembered'
|
||||
this command will be remembered
|
||||
|
||||
$ echo 'this command will be forgotten'
|
||||
this command will be forgotten
|
||||
```
|
||||
|
||||
Notice the leading space in the second command. Trying pressing your _up_ arrow
|
||||
and notice only that first `echo` is remembered.
|
||||
|
||||
Make sure `histignorespace` is included in the list when you run `setopt`. If
|
||||
it isn't, then add it:
|
||||
|
||||
```bash
|
||||
$ setopt histignorespace
|
||||
```
|
||||
|
||||
[source](https://stackoverflow.com/questions/8473121/execute-a-command-without-keeping-it-in-history/49643320#49643320)
|
||||
Reference in New Issue
Block a user