Skip to content

Commit

Permalink
feat: repo and worker setup (#16)
Browse files Browse the repository at this point in the history
Co-authored-by: Oli Evans <oli.evans@gmail.com>
  • Loading branch information
hugomrdias and olizilla committed Apr 28, 2022
1 parent 104480f commit 49c389f
Show file tree
Hide file tree
Showing 22 changed files with 1,911 additions and 1,248 deletions.
32 changes: 20 additions & 12 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,36 @@
"type": "module",
"main": "dist/worker.js",
"scripts": {
"lint": "tsc && eslint '**/*.{js,ts}' && prettier --check '**/*.{js,ts,md,yml,json}' --ignore-path ../../.gitignore",
"deploy": "wrangler publish --env production",
"predev": "./scripts/cli.js db --start && ./scripts/cli.js db-sql --cargo --testing --reset",
"dev": "miniflare --watch --debug --env ../../.env",
"clean": "./scripts/cli.js db --clean && rm -rf docker/compose",
"build": "scripts/cli.js build",
"test": "tsc && mocha",
"db-types": "./scripts/cli.js db-types"
"test": "tsc && ava"
},
"author": "Hugo Dias <hugomrdias@gmail.com> (hugodias.me)",
"license": "(Apache-2.0 OR MIT)",
"dependencies": {
"@cfworker/json-schema": "^1.8.3",
"@supabase/postgrest-js": "^0.37.2",
"merge-options": "^3.0.4"
"merge-options": "^3.0.4",
"nanoid": "^3.3.3",
"regexparam": "^2.0.0",
"toucan-js": "^2.6.0",
"ucan-storage": "^1.2.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^3.7.1",
"@sentry/cli": "^1.71.0",
"@sentry/webpack-plugin": "^1.16.0",
"@types/assert": "^1.5.6",
"@types/git-rev-sync": "^2.0.0",
"@types/mocha": "^9.1.1",
"assert": "^2.0.0",
"ava": "^4.2.0",
"buffer": "^6.0.3",
"delay": "^5.0.0",
"dotenv": "^16.0.0",
"esbuild": "^0.14.38",
"execa": "^6.1.0",
"git-rev-sync": "^3.0.1",
"miniflare": "^2.0.0-rc.3",
"mocha": "^9.2.2",
"openapi-typescript": "^5.2.0",
"playwright-test": "^7.2.1",
"process": "^0.11.10",
"readable-stream": "^3.6.0",
"sade": "^1.7.4"
Expand All @@ -55,12 +52,23 @@
},
"env": {
"mocha": true
},
"globals": {
"VERSION": "readonly",
"COMMITHASH": "readonly",
"BRANCH": "readonly",
"DEBUG": "readonly"
}
},
"eslintIgnore": [
"node_modules",
"coverage",
"dist",
"docs"
]
],
"ava": {
"ignoredByWatcher": [
"./dist/*"
]
}
}
8 changes: 5 additions & 3 deletions packages/api/scripts/cli.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env node
/* eslint-disable no-console */
import path from 'path'
import dotenv from 'dotenv'
import fs from 'fs'
Expand All @@ -19,6 +20,7 @@ dotenv.config({
})

const pkg = JSON.parse(
// eslint-disable-next-line unicorn/prefer-json-parse-buffer
fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')
)

Expand Down Expand Up @@ -60,7 +62,7 @@ prog
BRANCH: JSON.stringify(git.branch(__dirname)),
global: 'globalThis',
},
minify: opts.env === 'dev' ? false : true,
minify: opts.env !== 'dev',
sourcemap: true,
})

Expand Down Expand Up @@ -88,8 +90,8 @@ prog
env: opts.env,
})
}
} catch (err) {
console.error(err)
} catch (error) {
console.error(error)
process.exit(1)
}
})
Expand Down
1 change: 1 addition & 0 deletions packages/api/scripts/node-globals.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable unicorn/prefer-module */
// @ts-nocheck
export const { Buffer } = require('buffer')
export const process = require('process/browser')
21 changes: 21 additions & 0 deletions packages/api/src/bindings.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Logging } from './utils/logging'

export {}

declare global {
const _PRIVATE_KEY: string
const BRANCH: string
const VERSION: string
const COMMITHASH: string
const ENV: string
const DEBUG: string
}

export interface RouteContext {
params: Record<string, string>
log: Logging
}

export interface Handler {
(event: FetchEvent, ctx: RouteContext): Promise<Response> | Response
}
3 changes: 3 additions & 0 deletions packages/api/src/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* eslint-disable no-undef */

export const PRIVATE_KEY = _PRIVATE_KEY
34 changes: 20 additions & 14 deletions packages/api/src/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
/**
*
* @param {Request} request
* @returns
*/
async function handleRequest(request) {
return new Response(JSON.stringify({ msg: 'hello world!' }), {
headers: {
'content-type': 'application/json;charset=UTF-8',
},
})
}
import { Router } from './utils/router.js'
import { getContext } from './utils/context.js'
import { HTTPError } from './utils/errors.js'
import { cors, postCors } from './utils/cors.js'
// import { Service } from './service.js'
import { version } from './routes/version.js'
import { notFound } from './utils/responses.js'

addEventListener('fetch', (/** @type {FetchEvent} */ event) => {
return event.respondWith(handleRequest(event.request))
const r = new Router(getContext, {
onError(req, err, ctx) {
return HTTPError.respond(err, ctx)
},
})

// CORS
r.add('options', '*', cors)

// Version
r.add('get', '/version', version, [postCors])

r.add('all', '*', notFound)
addEventListener('fetch', r.listen.bind(r))
12 changes: 12 additions & 0 deletions packages/api/src/routes/version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { JSONResponse } from '../utils/responses.js'

/**
* @param {FetchEvent} event
*/
export function version(event) {
return new JSONResponse({
version: VERSION,
commit: COMMITHASH,
branch: BRANCH,
})
}
68 changes: 68 additions & 0 deletions packages/api/src/service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { UcanChain } from 'ucan-storage/ucan-chain'
import { KeyPair } from 'ucan-storage/keypair'

/**
* @type {import('ucan-storage/types').CapabilitySemantics<any>}
*/
export const accessSemantics = {
tryParsing(cap) {
return cap
},

tryDelegating(parentCap, childCap) {
return childCap
},
}

export class Service {
/**
* @param {KeyPair} keypair
*/
constructor(keypair) {
this.keypair = keypair
}

/**
* @param {string} key
*/
static async fromPrivateKey(key) {
const kp = await KeyPair.fromExportedKey(key)
return new Service(kp)
}

static async create() {
return new Service(await KeyPair.create())
}

/**
* Validates UCAN for capability
*
* @param {string} encodedUcan
*/
async validate(encodedUcan) {
const token = await UcanChain.fromToken(encodedUcan, {})

if (token.audience() !== this.did()) {
throw new Error('Invalid UCAN: Audience does not match this service.')
}

const caps = token.caps(accessSemantics)

if (caps.length > 1) {
throw new Error('Invocation ucan should have only 1 cap.')
}

const cap = caps[0]
const root = cap.root

if (root.issuer() !== this.did()) {
throw new Error('Invalid UCAN: Root issuer does not match this service.')
}

return cap
}

did() {
return this.keypair.did()
}
}
8 changes: 0 additions & 8 deletions packages/api/src/types.ts

This file was deleted.

35 changes: 35 additions & 0 deletions packages/api/src/utils/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Logging } from './logging.js'
// import Toucan from 'toucan-js'
// import pkg from '../../package.json'

// const sentryOptions = {
// dsn: secrets.sentry,
// allowedHeaders: ['user-agent', 'x-client'],
// allowedSearchParams: /(.*)/,
// debug: false,
// environment: ENV,
// rewriteFrames: {
// root: '/',
// },
// release: VERSION,
// pkg,
// }

/**
* Obtains a route context object.
*
* @param {FetchEvent} event
* @param {Record<string, string>} params - Parameters from the URL
* @returns {Promise<import('../bindings').RouteContext>}
*/
export async function getContext(event, params) {
// const sentry = new Toucan({
// event,
// ...sentryOptions,
// })
const log = new Logging(event, {
debug: DEBUG === 'true',
})

return { params, log }
}
54 changes: 54 additions & 0 deletions packages/api/src/utils/cors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* @param {FetchEvent} event
*/
export function cors(event) {
const headers = event.request.headers
// Make sure the necessary headers are present for this to be a valid pre-flight request
if (
headers.get('Origin') !== null &&
headers.get('Access-Control-Request-Method') !== null &&
headers.get('Access-Control-Request-Headers') !== null
) {
// Handle CORS pre-flight request.
/** @type {Record<string, string>} */
const respHeaders = {
'Content-Length': '0',
'Access-Control-Allow-Origin': headers.get('origin') || '*',
'Access-Control-Allow-Methods': 'GET,POST,DELETE,PATCH,OPTIONS',
'Access-Control-Max-Age': '86400',
// Allow all future content Request headers to go back to browser
// such as Authorization (Bearer) or X-Client-Name-Version
'Access-Control-Allow-Headers':
headers.get('Access-Control-Request-Headers') || '',
}

return new Response(undefined, {
status: 204,
headers: respHeaders,
})
} else {
return new Response('Non CORS options request not allowed', {
status: 405,
statusText: 'Method Not Allowed',
})
}
}

/**
* @param {Request} req
* @param {Response} rsp
*/
export function postCors(req, rsp) {
const origin = req.headers.get('origin')
if (origin) {
rsp.headers.set('Access-Control-Allow-Origin', origin)
rsp.headers.set(
'Access-Control-Allow-Methods',
'GET,POST,DELETE,PATCH,OPTIONS'
)
rsp.headers.set('Vary', 'Origin')
} else {
rsp.headers.set('Access-Control-Allow-Origin', '*')
}
return rsp
}
Loading

0 comments on commit 49c389f

Please sign in to comment.