Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3d0cbe0
commit 9b8bae8
Showing
13 changed files
with
380 additions
and
98 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
name: Cron sync goodbits list | ||
|
||
on: | ||
schedule: | ||
- cron: '13 0,5,10,15,20 * * *' | ||
workflow_dispatch: | ||
push: | ||
branches: | ||
- main | ||
paths: | ||
- 'packages/edge-gateway' | ||
|
||
jobs: | ||
update: | ||
name: Sync goodbits with KV store | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
env: ['staging', 'production'] | ||
timeout-minutes: 5 | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- uses: pnpm/action-setup@v2.0.1 | ||
with: | ||
version: 6.32.x | ||
- uses: actions/setup-node@v2 | ||
with: | ||
node-version: 16 | ||
cache: 'pnpm' | ||
- run: pnpm install | ||
- name: Run job | ||
env: | ||
ENV: ${{ matrix.env }} | ||
GH_TOKEN: ${{ secrets.GH_TOKEN }} | ||
CF_API_TOKEN: ${{ secrets.CF_GATEWAY_TOKEN }} | ||
run: pnpm --filter cron start:goodbits-sync |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#!/usr/bin/env node | ||
|
||
import { sync } from '../jobs/goodbits.js' | ||
import { envConfig } from '../lib/env.js' | ||
|
||
async function main() { | ||
await sync({ | ||
env: process.env.ENV || 'dev', | ||
}) | ||
} | ||
|
||
envConfig() | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import fetch, { Headers } from '@web-std/fetch' | ||
import path from 'path' | ||
import toml from 'toml' | ||
// @ts-ignore no types in module | ||
import ndjson from 'iterable-ndjson' | ||
|
||
/** | ||
* @typedef {{ id: string, title: string }} Namespace | ||
* @typedef {{ name: string, metadata: any }} Key | ||
* @typedef {{ key: string, value: any, metadata?: any }} BulkWritePair | ||
*/ | ||
|
||
const GOODBITS_SOURCES = [ | ||
'https://raw.githubusercontent.com/nftstorage/goodbits/main/list.ndjson', | ||
] | ||
|
||
const rootDir = path.dirname(path.dirname(import.meta.url)) | ||
const wranglerConfigPath = path.join( | ||
rootDir, | ||
'../../edge-gateway/wrangler.toml' | ||
) | ||
|
||
/** | ||
* @param {{ env: string } } opts | ||
*/ | ||
export async function sync({ env }) { | ||
const cfApiToken = mustGetEnv('CF_API_TOKEN') | ||
const ghToken = mustGetEnv('GH_TOKEN') | ||
|
||
const wranglerConfig = await getWranglerToml(wranglerConfigPath) | ||
const wranglerEnvConfig = wranglerConfig.env[env] | ||
if (!wranglerEnvConfig) { | ||
throw new Error(`missing wrangler configuration for env: ${env}`) | ||
} | ||
console.log(`🧩 using wrangler config: ${wranglerConfigPath}`) | ||
|
||
const cfAccountId = wranglerEnvConfig.account_id | ||
if (!cfAccountId) { | ||
throw new Error(`missing Cloudflare account_id in env: ${env}`) | ||
} | ||
console.log(`🏕 using env: ${env} (${cfAccountId})`) | ||
|
||
const kvNamespaces = wranglerEnvConfig.kv_namespaces || [] | ||
const goodbitsListKv = kvNamespaces.find( | ||
(kv) => kv.binding === 'GOODBITSLIST' | ||
) | ||
if (!goodbitsListKv) { | ||
throw new Error('missing binding in kv_namespaces: GOODBITSLIST') | ||
} | ||
console.log(`🪢 using KV binding: GOODBITSLIST (${goodbitsListKv.id})`) | ||
|
||
for (const url of GOODBITS_SOURCES) { | ||
console.log(`🦴 fetching ${url}`) | ||
const goodbitsList = await getGoodbitsList(url, ghToken) | ||
|
||
const kvs = [] | ||
for await (const { cid, tags } of goodbitsList) { | ||
kvs.push({ | ||
key: cid, | ||
value: { tags }, | ||
}) | ||
} | ||
|
||
console.log(`📝 writing ${kvs.length} entries`) | ||
await writeKVMulti(cfApiToken, cfAccountId, goodbitsListKv.id, kvs) | ||
} | ||
|
||
console.log('✅ Done') | ||
} | ||
|
||
/** | ||
* @param {string} apiToken Cloudflare API token | ||
* @param {string} accountId Cloudflare account ID | ||
* @param {string} nsId KV namespace ID | ||
* @param {Array<BulkWritePair>} kvs | ||
* @returns {Promise<void>} | ||
*/ | ||
async function writeKVMulti(apiToken, accountId, nsId, kvs) { | ||
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${nsId}/bulk` | ||
kvs = kvs.map((kv) => ({ | ||
...kv, | ||
value: JSON.stringify(kv.value), | ||
})) | ||
|
||
const chunkSize = 10000 | ||
for (let i = 0; i < kvs.length; i += chunkSize) { | ||
const kvsChunk = kvs.slice(i, i + chunkSize) | ||
const res = await fetch(url, { | ||
method: 'PUT', | ||
headers: { | ||
Authorization: `Bearer ${apiToken}`, | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify(kvsChunk), | ||
}) | ||
const { success, errors } = await res.json() | ||
if (!success) { | ||
const error = Array.isArray(errors) && errors[0] | ||
throw new Error( | ||
error ? `${error.code}: ${error.message}` : 'failed to write to KV' | ||
) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @param {string} url | ||
* @param {string} ghToken | ||
*/ | ||
async function getGoodbitsList(url, ghToken) { | ||
const headers = new Headers() | ||
headers.append('authorization', `token ${ghToken}`) | ||
headers.append('cache-control', 'no-cache') | ||
headers.append('pragma', 'no-cache') | ||
|
||
const res = await fetch(url, { | ||
headers, | ||
}) | ||
if (!res.ok) { | ||
throw new Error(`unexpected status fetching goodbits list: ${res.status}`) | ||
} | ||
return ndjson.parse(await res.text()) | ||
} | ||
|
||
async function getWranglerToml(url) { | ||
const res = await fetch(url) | ||
if (!res.ok) { | ||
throw new Error(`unexpected status fetching wrangler.toml: ${res.status}`) | ||
} | ||
return toml.parse(await res.text()) | ||
} | ||
|
||
/** | ||
* @param {string} key | ||
* @returns {string} | ||
*/ | ||
function mustGetEnv(key) { | ||
if (process.env[key]) { | ||
throw new Error(`missing environment variable: ${key}`) | ||
} | ||
// @ts-ignore validation of undefined before not accepted by ts compiler | ||
return process.env[key] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
packages/edge-gateway/test/content_security_policy.spec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { test, getMiniflare } from './utils/setup.js' | ||
|
||
test.beforeEach((t) => { | ||
// Create a new Miniflare environment for each test | ||
t.context = { | ||
mf: getMiniflare(), | ||
} | ||
}) | ||
|
||
test('Gets content from binding', async (t) => { | ||
const { mf } = t.context | ||
|
||
const response = await mf.dispatchFetch( | ||
'https://bafkreidyeivj7adnnac6ljvzj2e3rd5xdw3revw4da7mx2ckrstapoupoq.ipfs.localhost:8787' | ||
) | ||
await response.waitUntil() | ||
t.is(await response.text(), 'Hello nftstorage.link! 😎') | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.