Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: replace kv with cheaper alternative d1 #5

Merged
merged 10 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .dev.vars.example

This file was deleted.

16 changes: 0 additions & 16 deletions .editorconfig

This file was deleted.

45 changes: 33 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
name: Checks

#
# NOTE:
# everything under the 'checkout' step is temporary until I figure out how to use devenv.sh in gh actions
# devenv has a guide for actions: https://devenv.sh/integrations/github-actions
# but it doesn't work if you are using devenv this way: https://devenv.sh/guides/using-with-flakes

on:
pull_request:
workflow_dispatch:
Expand All @@ -13,7 +19,6 @@ defaults:

env:
NAME: 'url-shortener'
CARGO_TERM_COLOR: 'always'
ACTIONS_RUNNER_DEBUG: true

jobs:
Expand All @@ -23,21 +28,37 @@ jobs:
- name: 🔑 Checkout
uses: actions/checkout@v4

- name: Install Nix
uses: cachix/install-nix-action@v30
- name: 🦀 Set up Rust
uses: dtolnay/rust-toolchain@nightly
with:
targets: wasm32-unknown-unknown
components: rustc, cargo, rustfmt, clippy

- name: Setup Rust Cache
uses: Swatinem/[email protected]
with:
prefix-key: v0 # increment this to bust the cache if needed

- name: Install sccache
uses: mozilla-actions/[email protected]
env:
RUSTC_WRAPPER: 'sccache'
SCCACHE_GHA_ENABLED: true

- name: Setup Cachix
uses: cachix/cachix-action@v15
- name: 🐰 Set up Bun
uses: oven-sh/setup-bun@main
with:
name: 'devenv'
bun-version: 'latest'

- name: Install devenv.sh
run: nix profile install nixpkgs#devenv
- name: Format
run: |
bunx @taplo/cli@latest fmt *.toml
cargo fmt --all --check

- name: Lint
run: |
fmt
lint
bunx @taplo/cli@latest lint *.toml
cargo clippy --all-targets --all-features -- -D warnings

- name: Build
run: build
- name: 🛠️ Build worker
run: cargo install --quiet worker-build && worker-build --release
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: Deploy
on:
push:
branches: [main]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -13,7 +14,6 @@ defaults:

env:
NAME: 'url-shortener'
CARGO_TERM_COLOR: 'always'
ACTIONS_RUNNER_DEBUG: true

jobs:
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ crate-type = ["cdylib"]
[dependencies]
url = "2.5.3"
serde = "1.0.214"
serde_json = "1.0.104"
# needed to enable the "js" feature for compatibility with wasm,
# see https://docs.rs/getrandom/#webassembly-support
getrandom = { version = "0.2", features = ["js"] }
worker = { version = "0.4.2", features = ['http', 'axum'] }
worker = { version = "0.4.2", features = ['http', 'axum', 'd1'] }
uuid = { version = "1.11.0", features = [
"v4",
"fast-rng",
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# URL Shortener - Cloudflare Worker

## Usage

> [!NOTE]
Expand Down
17 changes: 15 additions & 2 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
description = "URL Shortener Worker";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
systems.url = "github:nix-systems/default";
Expand Down Expand Up @@ -37,8 +38,14 @@
modules = [
{
# https://devenv.sh/reference/options/
languages.nix.enable = true;
scripts = import ./tasks.nix;

dotenv = {
enable = true;
filename = [ ".env" ];
};

languages.nix.enable = true;
languages.rust = {
enable = true;
channel = "nightly";
Expand All @@ -49,19 +56,25 @@
"clippy"
"rustfmt"
"rust-analyzer"

];
};

# for development only
# this is the default location when you run d1 with `--local`
env.D1_DATABASE_FILEPATH = ".wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.db";

packages = with pkgs; [
jq
git
bun
taplo
direnv
sqlfluff
binaryen
nixfmt-rfc-style
nodePackages_latest.nodejs
];
scripts = import ./tasks.nix;
}
];
};
Expand Down
4 changes: 4 additions & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
max_width = 100
reorder_imports = true
imports_granularity = "Crate"
group_imports = "StdExternalCrate"
8 changes: 8 additions & 0 deletions schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
PRAGMA foreign_keys = OFF;
DROP TABLE IF EXISTS urls;

CREATE TABLE urls (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
url TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
14 changes: 14 additions & 0 deletions scripts/seed.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail

# seed local d1 database with data

urls=(
"https://docs.union.build/reference/graphql/?query=%7B__typename%7D"
"https://docs.union.build/reference/graphql/?query=%7B%0A%20%20v1_daily_transfers%20%7B%0A%20%20%20%20count%0A%20%20%20%20day%0A%20%20%7D%0A%7D"
"https://docs.union.build/reference/graphql/?query=%7B%0A%20%20get_route(%0A%20%20%20%20args%3A%20%7Bdestination_chain_id%3A%20%22stride-internal-1%22%2C%20receiver%3A%20%22me%22%2C%20source_chain_id%3A%20%2211155111%22%2C%20forward_chain_id%3A%20%22union-testnet-8%22%7D%0A%20%20)%20%7B%0A%20%20%20%20memo%0A%20%20%7D%0A%7D"
)

for url in "${urls[@]}"; do
d1-query --local --command="INSERT INTO urls (url) VALUES ('$url');"
done
73 changes: 50 additions & 23 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use url::Url;
use uuid::Uuid;
use worker::*;

#[derive(Debug, Deserialize, Serialize)]
Expand All @@ -11,16 +11,27 @@ struct GenericResponse {

#[event(fetch)]
async fn main(request: Request, env: Env, _context: Context) -> Result<Response> {
Router::new()
let environment = env.var("ENVIRONMENT").unwrap().to_string();
if environment.trim().is_empty() {
return Response::error("not allowed", 403);
}

let mut router = Router::new()
// public routes
.get("/", index_route)
.post("/", index_route)
.post_async("/create", handle_create)
.get_async("/:key", handle_url_expand)
.run(request, env)
.await
.get_async("/:key", handle_url_expand);

if environment == "development" {
// dev-only routes
// quick way to check records are inserted
router = router.get_async("/list", dev_handle_list_urls);
}

return router.run(request, env).await;
}

// handles `GET /` and `POST /`
pub fn index_route(_request: Request, _context: RouteContext<()>) -> worker::Result<Response> {
Response::ok("zkgm")
}
Expand All @@ -35,37 +46,53 @@ pub async fn handle_create(
return Response::error("provided url is not valid", 400);
}

let random_uuid = Uuid::new_v4();
let key = random_uuid.to_string()[0..6].to_string();
let insert_new = context.kv("KV")?.put(&key, url).unwrap().execute().await;
let d1 = context.env.d1("DB");
let statement = d1?.prepare("INSERT INTO urls (url) VALUES (?)");
let query = statement.bind(&[url.into()]);
let result = query?.run().await?.success();

if insert_new.is_err() {
return Response::error("failed to insert new key", 500);
if result {
return Response::ok("ok");
}

Response::ok(&key)
Response::error("failed to insert new key", 500)
}

// checks `GET /:key{[0-9a-z]{6}}`
// checks `GET /:key{[0-9]}`
pub async fn handle_url_expand(
request: Request,
context: RouteContext<()>,
) -> worker::Result<Response> {
let key = &request.path().to_string()[1..];
if key.len() != 6 || !key.chars().all(|char| char.is_alphanumeric()) {
if key.parse::<u64>().is_err() {
return Response::error("invalid key: ".to_string() + key, 400);
}

let expanded_url = context.kv("KV")?.get(key).text().await?;
if expanded_url.is_some() {
return Response::redirect(Url::parse(&expanded_url.unwrap()).unwrap());
let d1 = context.env.d1("DB");
let statement = d1?.prepare("SELECT url FROM urls WHERE id = ?");
let query = statement.bind(&[key.into()]);
let result: Option<Value> = query?.first::<Value>(None).await?;

match result {
Some(Value::Object(object)) => {
if let Some(Value::String(url)) = object.get("url") {
return Response::redirect(Url::parse(url)?);
}
Response::error("Invalid URL format", 400)
}
_ => Response::error("Invalid key: ".to_string() + key, 400),
}
}

let environment = context.env.var("ENVIRONMENT").unwrap().to_string();
let base_url = match environment.as_str() {
"development" => "http://localhost:8787",
_ => &request.url().unwrap().origin().ascii_serialization(),
};
pub async fn dev_handle_list_urls(
_request: Request,
context: RouteContext<()>,
) -> worker::Result<Response> {
let d1 = context.env.d1("DB");
let statement = d1?.prepare("SELECT * FROM urls");
let query = statement.bind(&[]);
let result = query?.all().await?;

Response::redirect(Url::parse(base_url).unwrap())
let urls: Vec<Value> = result.results()?;
Response::from_json(&urls)
}
30 changes: 26 additions & 4 deletions tasks.nix
Original file line number Diff line number Diff line change
@@ -1,25 +1,47 @@
{
wrangler.exec = ''
bunx wrangler@latest --config='wrangler.toml' "$@"
'';
fmt.exec = ''
taplo fmt *.toml
cargo fmt --all --check
nixfmt *.nix --width=100
cargo fmt --all -- --config-path=rustfmt.toml
sqlfluff format --dialect sqlite ./schema.sql
'';
lint.exec = ''
taplo lint *.toml
cargo clippy --all-targets --all-features
sqlfluff lint --dialect sqlite ./schema.sql
'';
build.exec = ''
cargo build --release --target wasm32-unknown-unknown
'';
# optional: `--remote`
dev.exec = ''
bunx wrangler@latest --config='wrangler.toml' dev
bunx wrangler@latest --config='wrangler.toml' dev "$@"
'';
# optional: `--local`, `--remote`
d1-bootstrap.exec = ''
bunx wrangler@latest --config='wrangler.toml' d1 execute url-short-d1 --file='schema.sql' "$@"
'';
# optional: `--local`, `--remote`
# required: `--command="SELECT * FROM urls"`
d1-query.exec = ''
bunx wrangler@latest --config='wrangler.toml' d1 execute url-short-d1 "$@"
'';
dev-remote.exec = ''
bunx wrangler@latest --config='wrangler.toml' dev --remote
d1-seed.exec = ''
bash ./scripts/seed.sh
'';
# only works locally in development
d1-viewer.exec = ''
bunx @outerbase/studio@latest $D1_DATABASE_FILEPATH --port=4000
'';
deploy.exec = ''
bunx wrangler@latest deploy --env='production' --config='wrangler.toml'
'';
rustdoc.exec = ''
cargo rustdoc -- --default-theme='ayu'
'';
clean.exec = ''
rm -rf build
rm -rf target
Expand Down
10 changes: 6 additions & 4 deletions wrangler.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ compatibility_date = "2024-11-01"
workers_dev = true
vars = { ENVIRONMENT = "development" }

kv_namespaces = [
{ binding = "KV", id = "96961fcf1ee84e3891cba0c326efe769", preview_id = "a4134835b7c74de38137d6545155aea0" },
d1_databases = [
{ binding = "DB", database_name = "url-short-d1", database_id = "3da5b327-e066-4915-a8dd-22cddbcbcf0b" },
]

[build]
Expand All @@ -19,9 +19,11 @@ command = "cargo install --quiet worker-build && worker-build --release"
name = "url-shortener"
workers_dev = true
vars = { ENVIRONMENT = "production" }
kv_namespaces = [
{ binding = "KV", id = "96961fcf1ee84e3891cba0c326efe769", preview_id = "a4134835b7c74de38137d6545155aea0" },

d1_databases = [
{ binding = "DB", database_name = "url-short-d1", database_id = "3da5b327-e066-4915-a8dd-22cddbcbcf0b" },
]

# https://developers.cloudflare.com/workers/observability/logs/workers-logs/
[env.production.observability]
enabled = true
Loading