1
0
mirror of synced 2026-02-06 09:00:12 -05:00

Compare commits

..

1 Commits
db ... bug

Author SHA1 Message Date
Brandon Bayer
c8700453a4 add pages 2020-09-24 12:16:46 -04:00
409 changed files with 11958 additions and 17248 deletions

View File

@@ -353,8 +353,7 @@
"profile": "https://github.com/ntgussoni",
"contributions": [
"test",
"code",
"review"
"code"
]
},
{
@@ -609,8 +608,7 @@
"avatar_url": "https://avatars2.githubusercontent.com/u/1329874?v=4",
"profile": "https://jamiedavenport.dev",
"contributions": [
"code",
"maintenance"
"code"
]
},
{
@@ -973,8 +971,7 @@
"avatar_url": "https://avatars2.githubusercontent.com/u/37571416?v=4",
"profile": "https://github.com/clgeoio",
"contributions": [
"code",
"test"
"code"
]
},
{
@@ -994,8 +991,7 @@
"contributions": [
"code",
"maintenance",
"question",
"doc"
"question"
]
},
{
@@ -1004,8 +1000,7 @@
"avatar_url": "https://avatars2.githubusercontent.com/u/1430136?v=4",
"profile": "https://github.com/sirmyron",
"contributions": [
"doc",
"code"
"doc"
]
},
{
@@ -1014,9 +1009,8 @@
"avatar_url": "https://avatars1.githubusercontent.com/u/36962022?v=4",
"profile": "https://github.com/engelkes-finstreet",
"contributions": [
"doc",
"code",
"maintenance"
"doc"
]
},
{
@@ -1174,575 +1168,6 @@
"contributions": [
"code"
]
},
{
"login": "jorisre",
"name": "Joris",
"avatar_url": "https://avatars1.githubusercontent.com/u/7545547?v=4",
"profile": "https://github.com/jorisre",
"contributions": [
"code"
]
},
{
"login": "Kamshak",
"name": "Valentin Funk",
"avatar_url": "https://avatars3.githubusercontent.com/u/337968?v=4",
"profile": "https://github.com/Kamshak",
"contributions": [
"doc"
]
},
{
"login": "lukebennett",
"name": "Luke Bennett",
"avatar_url": "https://avatars1.githubusercontent.com/u/135390?v=4",
"profile": "https://lukebennett.com",
"contributions": [
"code"
]
},
{
"login": "hmajid2301",
"name": "Haseeb Majid",
"avatar_url": "https://avatars0.githubusercontent.com/u/998807?v=4",
"profile": "https://haseebmajid.dev",
"contributions": [
"code"
]
},
{
"login": "phillippschmedt",
"name": "Phillipp Schmedt",
"avatar_url": "https://avatars0.githubusercontent.com/u/16028406?v=4",
"profile": "https://github.com/phillippschmedt",
"contributions": [
"code"
]
},
{
"login": "hasparus",
"name": "Piotr Monwid-Olechnowicz",
"avatar_url": "https://avatars0.githubusercontent.com/u/15332326?v=4",
"profile": "https://haspar.us",
"contributions": [
"code"
]
},
{
"login": "mizchi",
"name": "Kotaro Chikuba",
"avatar_url": "https://avatars2.githubusercontent.com/u/73962?v=4",
"profile": "https://mizchi.dev",
"contributions": [
"code",
"test"
]
},
{
"login": "konradkalemba",
"name": "Konrad Kalemba",
"avatar_url": "https://avatars0.githubusercontent.com/u/8682104?v=4",
"profile": "https://github.com/konradkalemba",
"contributions": [
"code"
]
},
{
"login": "Alucard17",
"name": "Alucard17",
"avatar_url": "https://avatars1.githubusercontent.com/u/26205172?v=4",
"profile": "https://github.com/Alucard17",
"contributions": [
"code"
]
},
{
"login": "Dohxis",
"name": "Domantas Mauruča",
"avatar_url": "https://avatars2.githubusercontent.com/u/8768909?v=4",
"profile": "https://github.com/Dohxis",
"contributions": [
"test",
"code"
]
},
{
"login": "sandulat",
"name": "Stratulat Alexandru",
"avatar_url": "https://avatars0.githubusercontent.com/u/7345874?v=4",
"profile": "https://sandulat.com/",
"contributions": [
"code",
"maintenance"
]
},
{
"login": "aericson",
"name": "André Ericson",
"avatar_url": "https://avatars3.githubusercontent.com/u/692542?v=4",
"profile": "https://github.com/aericson",
"contributions": [
"code",
"doc"
]
},
{
"login": "cajotafer",
"name": "Carlos Fernández",
"avatar_url": "https://avatars2.githubusercontent.com/u/41461969?v=4",
"profile": "http://Cajotafer.com",
"contributions": [
"doc"
]
},
{
"login": "Kosai106",
"name": "Kevin Østerkilde",
"avatar_url": "https://avatars1.githubusercontent.com/u/6379824?v=4",
"profile": "https://oesterkilde.dk/",
"contributions": [
"doc",
"code"
]
},
{
"login": "aaronfulkerson",
"name": "aaronfulkerson",
"avatar_url": "https://avatars0.githubusercontent.com/u/31112737?v=4",
"profile": "https://github.com/aaronfulkerson",
"contributions": [
"code",
"question"
]
},
{
"login": "alexnaiman",
"name": "Alexandru Naiman",
"avatar_url": "https://avatars3.githubusercontent.com/u/25799714?v=4",
"profile": "https://github.com/alexnaiman",
"contributions": [
"code"
]
},
{
"login": "davidlutta",
"name": "David Ezekiel Lutta",
"avatar_url": "https://avatars2.githubusercontent.com/u/14890315?v=4",
"profile": "https://davidlutta.github.io/portfolio/",
"contributions": [
"code"
]
},
{
"login": "wanjuntham",
"name": "wanjuntham",
"avatar_url": "https://avatars1.githubusercontent.com/u/49380551?v=4",
"profile": "https://github.com/wanjuntham",
"contributions": [
"code"
]
},
{
"login": "nahue",
"name": "Victor Nahuel Chaves",
"avatar_url": "https://avatars3.githubusercontent.com/u/96837?v=4",
"profile": "http://www.nahuelchaves.xyz",
"contributions": [
"code"
]
},
{
"login": "peter50216",
"name": "Peter Shih",
"avatar_url": "https://avatars3.githubusercontent.com/u/891109?v=4",
"profile": "https://github.com/peter50216",
"contributions": [
"code"
]
},
{
"login": "sewerynkalemba",
"name": "Seweryn Kalemba",
"avatar_url": "https://avatars3.githubusercontent.com/u/37031328?v=4",
"profile": "http://seweryn.kale.mba",
"contributions": [
"code"
]
},
{
"login": "nksaraf",
"name": "Nikhil Saraf",
"avatar_url": "https://avatars2.githubusercontent.com/u/11255148?v=4",
"profile": "https://nksaraf.github.io",
"contributions": [
"code",
"doc"
]
},
{
"login": "zanedb",
"name": "Zane",
"avatar_url": "https://avatars0.githubusercontent.com/u/16865690?v=4",
"profile": "https://zane.sh",
"contributions": [
"doc"
]
},
{
"login": "dulcehc",
"name": "Dulce Hernández",
"avatar_url": "https://avatars1.githubusercontent.com/u/19391835?v=4",
"profile": "https://github.com/dulcehc",
"contributions": [
"code"
]
},
{
"login": "markhaehnel",
"name": "Mark Hähnel",
"avatar_url": "https://avatars2.githubusercontent.com/u/1516205?v=4",
"profile": "https://markhaehnel.de",
"contributions": [
"code"
]
},
{
"login": "nemesv",
"name": "Viktor Nemes",
"avatar_url": "https://avatars0.githubusercontent.com/u/251330?v=4",
"profile": "http://stackoverflow.com/users/872395/nemesv",
"contributions": [
"code"
]
},
{
"login": "goleary",
"name": "Gabe O'Leary",
"avatar_url": "https://avatars1.githubusercontent.com/u/16123225?v=4",
"profile": "http://gabeoleary.com",
"contributions": [
"doc"
]
},
{
"login": "machadolucasvp",
"name": "Lucas Machado",
"avatar_url": "https://avatars0.githubusercontent.com/u/44952113?v=4",
"profile": "https://github.com/machadolucasvp",
"contributions": [
"code"
]
},
{
"login": "maciekgrzybek",
"name": "maciek_grzybek",
"avatar_url": "https://avatars2.githubusercontent.com/u/16546428?v=4",
"profile": "https://github.com/maciekgrzybek",
"contributions": [
"code"
]
},
{
"login": "mweibel",
"name": "Michael Weibel",
"avatar_url": "https://avatars1.githubusercontent.com/u/307427?v=4",
"profile": "https://github.com/mweibel",
"contributions": [
"code"
]
},
{
"login": "isoppp",
"name": "Hiroki Isogai",
"avatar_url": "https://avatars0.githubusercontent.com/u/16318727?v=4",
"profile": "https://isoppp.com",
"contributions": [
"code"
]
},
{
"login": "matamatanot",
"name": "matamatanot",
"avatar_url": "https://avatars2.githubusercontent.com/u/39780486?v=4",
"profile": "https://github.com/matamatanot",
"contributions": [
"doc"
]
},
{
"login": "ericsakmar",
"name": "Eric Sakmar",
"avatar_url": "https://avatars3.githubusercontent.com/u/5620709?v=4",
"profile": "https://github.com/ericsakmar",
"contributions": [
"doc"
]
},
{
"login": "leggsimon",
"name": "Simon Legg",
"avatar_url": "https://avatars2.githubusercontent.com/u/11544418?v=4",
"profile": "https://github.com/leggsimon",
"contributions": [
"doc"
]
},
{
"login": "wobsoriano",
"name": "Robert Soriano",
"avatar_url": "https://avatars3.githubusercontent.com/u/13049130?v=4",
"profile": "https://robsoriano.com",
"contributions": [
"code"
]
},
{
"login": "benediktms",
"name": "Benedikt Schnatterbeck",
"avatar_url": "https://avatars2.githubusercontent.com/u/48836135?v=4",
"profile": "https://github.com/benediktms",
"contributions": [
"code"
]
},
{
"login": "Talor-A",
"name": "Talor Anderson",
"avatar_url": "https://avatars2.githubusercontent.com/u/11509865?v=4",
"profile": "http://taloranderson.com",
"contributions": [
"code"
]
},
{
"login": "akirabaruah",
"name": "Akira Baruah",
"avatar_url": "https://avatars2.githubusercontent.com/u/6751517?v=4",
"profile": "https://github.com/akirabaruah",
"contributions": [
"code"
]
},
{
"login": "cwray-tech",
"name": "Christopher Wray",
"avatar_url": "https://avatars0.githubusercontent.com/u/53663762?v=4",
"profile": "https://chriswray.dev/",
"contributions": [
"code"
]
},
{
"login": "piotrski",
"name": "Piotrek Tomczewski",
"avatar_url": "https://avatars0.githubusercontent.com/u/244174?v=4",
"profile": "https://github.com/piotrski",
"contributions": [
"code"
]
},
{
"login": "rap2hpoutre",
"name": "Raphaël Huchet",
"avatar_url": "https://avatars3.githubusercontent.com/u/1575946?v=4",
"profile": "http://raph.site",
"contributions": [
"doc",
"test",
"code"
]
},
{
"login": "KATT",
"name": "Alex Johansson",
"avatar_url": "https://avatars1.githubusercontent.com/u/459267?v=4",
"profile": "http://kattcorp.com",
"contributions": [
"code"
]
},
{
"login": "dmzza",
"name": "David Mazza",
"avatar_url": "https://avatars0.githubusercontent.com/u/120893?v=4",
"profile": "http://davidmazza.com",
"contributions": [
"code"
]
},
{
"login": "rayandrews",
"name": "Ray Andrew",
"avatar_url": "https://avatars1.githubusercontent.com/u/4437323?v=4",
"profile": "https://github.com/rayandrews",
"contributions": [
"code",
"doc"
]
},
{
"login": "Mzaien",
"name": "Abdullah Mzaien",
"avatar_url": "https://avatars3.githubusercontent.com/u/43112535?v=4",
"profile": "http://Dal.Design",
"contributions": [
"code"
]
},
{
"login": "williamkwao",
"name": "William Kwao",
"avatar_url": "https://avatars2.githubusercontent.com/u/8839514?v=4",
"profile": "http://kwao.io",
"contributions": [
"doc"
]
},
{
"login": "sakulstra",
"name": "Lukas Strassel",
"avatar_url": "https://avatars3.githubusercontent.com/u/4396533?v=4",
"profile": "https://github.com/sakulstra",
"contributions": [
"code"
]
},
{
"login": "tpatel",
"name": "Thibaut Patel",
"avatar_url": "https://avatars3.githubusercontent.com/u/494686?v=4",
"profile": "https://thibpat.com",
"contributions": [
"code"
]
},
{
"login": "jonstuebe",
"name": "Jon Stuebe",
"avatar_url": "https://avatars0.githubusercontent.com/u/156722?v=4",
"profile": "http://jonstuebe.com",
"contributions": [
"code"
]
},
{
"login": "ugogo",
"name": "Ugo Onali",
"avatar_url": "https://avatars2.githubusercontent.com/u/5040476?v=4",
"profile": "https://ugogo.dev",
"contributions": [
"doc"
]
},
{
"login": "saintmalik",
"name": "SaintMalik",
"avatar_url": "https://avatars1.githubusercontent.com/u/37118134?v=4",
"profile": "https://saintmalik.me",
"contributions": [
"doc"
]
},
{
"login": "Khaledgarbaya",
"name": "Khaled Garbaya",
"avatar_url": "https://avatars1.githubusercontent.com/u/1156093?v=4",
"profile": "https://khaledgarbaya.net",
"contributions": [
"code"
]
},
{
"login": "tundera",
"name": "tundera",
"avatar_url": "https://avatars0.githubusercontent.com/u/61833561?v=4",
"profile": "https://tundera.dev",
"contributions": [
"code"
]
},
{
"login": "markylaing",
"name": "markylaing",
"avatar_url": "https://avatars2.githubusercontent.com/u/41469221?v=4",
"profile": "https://github.com/markylaing",
"contributions": [
"code",
"doc"
]
},
{
"login": "AkifumiSato",
"name": "Akifumi Sato",
"avatar_url": "https://avatars2.githubusercontent.com/u/25711332?v=4",
"profile": "https://akfm.dev/",
"contributions": [
"code"
]
},
{
"login": "beeplin",
"name": "Beep LIN",
"avatar_url": "https://avatars3.githubusercontent.com/u/13058150?v=4",
"profile": "https://github.com/beeplin",
"contributions": [
"code"
]
},
{
"login": "mattfwood",
"name": "Matt Wood",
"avatar_url": "https://avatars1.githubusercontent.com/u/22530815?v=4",
"profile": "https://mattwood.tech/",
"contributions": [
"code"
]
},
{
"login": "jackbravo",
"name": "Joaquin Bravo Contreras",
"avatar_url": "https://avatars1.githubusercontent.com/u/15214?v=4",
"profile": "http://joaquin.axai.mx",
"contributions": [
"code"
]
},
{
"login": "arjundubey-cr",
"name": "Arjun Dubey",
"avatar_url": "https://avatars0.githubusercontent.com/u/40758425?v=4",
"profile": "https://github.com/arjundubey-cr",
"contributions": [
"code"
]
},
{
"login": "chanand",
"name": "chanand",
"avatar_url": "https://avatars0.githubusercontent.com/u/1317789?v=4",
"profile": "https://github.com/chanand",
"contributions": [
"code"
]
},
{
"login": "phillipkregg",
"name": "phillipkregg",
"avatar_url": "https://avatars0.githubusercontent.com/u/1066044?v=4",
"profile": "https://github.com/phillipkregg",
"contributions": [
"doc"
]
},
{
"login": "timReynolds",
"name": "Tim Reynolds",
"avatar_url": "https://avatars1.githubusercontent.com/u/168870?v=4",
"profile": "http://timothyreynolds.co.uk",
"contributions": [
"doc"
]
}
],
"contributorsPerLine": 7,

View File

@@ -8,7 +8,7 @@ module.exports = {
},
project: `./tsconfig.json`,
},
plugins: ["@typescript-eslint", "import", "unicorn", "simple-import-sort"],
plugins: ["@typescript-eslint", "import", "unicorn"],
extends: ["react-app"],
rules: {
"react/react-in-jsx-scope": "off", // React is always in scope with Blitz
@@ -24,32 +24,7 @@ module.exports = {
},
],
"@typescript-eslint/no-floating-promises": "error",
// note you must disable the base rule as it can report incorrect errors
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": ["error"],
// note you must disable the base rule as it can report incorrect errors
"no-redeclare": "off",
"@typescript-eslint/no-redeclare": ["error"],
"simple-import-sort/imports": [
"warn",
{
groups: [
[
// Side effect imports.
"^\\u0000",
// Packages.
// Things that start with a letter (or digit or underscore), or `@` followed by a letter.
"^@?\\w",
// Absolute imports and other imports such as Vue-style `@/foo`.
// Anything that does not start with a dot.
"^[^.]",
// Relative imports.
// Anything that starts with a dot.
"^\\.",
],
],
},
],
"no-use-before-define": ["error", {functions: false, classes: false}],
},
ignorePatterns: ["packages/cli/", "packages/generator/templates", ".eslintrc.js"],
overrides: [

9
.github/CODEOWNERS vendored
View File

@@ -1,8 +1,11 @@
# https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners
* @flybayer
*.md @merelinguist
packages/cli/**/* @aem, @flybayer
packages/generator/**/* @aem @flybayer
packages/server/**/* @ryardley
packages/file-pipeline/**/* @ryardley
packages/cli/**/* @aem
packages/generator/**/* @aem
packages/generator/templates**/* @flybayer
packages/installer/**/* @aem @flybayer
packages/installer/**/* @aem

View File

@@ -1,4 +1,4 @@
name: CI
name: Compressed Size
on:
pull_request:
@@ -6,15 +6,14 @@ on:
jobs:
build:
name: Compressed Size
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node and Yarn
uses: actions/setup-node@v2
uses: actions/setup-node@v1
with:
node-version: "14"
node-version: "12.x"
- name: Install dependencies
run: |
yarn && yarn build

View File

@@ -1,4 +1,4 @@
name: CI
name: Continuous Integration
on:
push:
@@ -17,9 +17,9 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v1
with:
node-version: "14"
node-version: "12.16.1"
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
@@ -32,9 +32,9 @@ jobs:
**/node_modules
/home/runner/.cache/Cypress
C:\Users\runneradmin\AppData\Local\Cypress\Cache
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v4-${{ hashFiles('yarn.lock') }}
key: ${{ runner.os }}-yarn-v3-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-${{ runner.node_version}}-yarn-v4-
${{ runner.os }}-yarn-v3-
- name: Install dependencies
run: yarn install --frozen-lockfile --silent
env:
@@ -44,18 +44,17 @@ jobs:
env:
CI: true
build_and_test_pkgs:
name: Packages Tests
name: Build & Test Packages
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node_version: [12, 14]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node_version }}
node-version: "12.16.1"
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
@@ -67,9 +66,9 @@ jobs:
${{ steps.yarn-cache-dir-path.outputs.dir }}
/home/runner/.cache/Cypress
C:\Users\runneradmin\AppData\Local\Cypress\Cache
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v4-${{ hashFiles('yarn.lock') }}
key: ${{ runner.os }}-yarn-v2-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-${{ runner.node_version}}-yarn-v4-
${{ runner.os }}-yarn-v2-
- name: Install dependencies
run: yarn install --frozen-lockfile --silent
env:
@@ -86,19 +85,17 @@ jobs:
env:
CI: true
build_and_test_examples:
timeout-minutes: 30
name: Example Apps Tests
name: Build & Test Examples
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node_version: [12, 14]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node_version }}
node-version: "12.16.1"
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
@@ -110,9 +107,9 @@ jobs:
${{ steps.yarn-cache-dir-path.outputs.dir }}
/home/runner/.cache/Cypress
C:\Users\runneradmin\AppData\Local\Cypress\Cache
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v4-${{ hashFiles('yarn.lock') }}
key: ${{ runner.os }}-yarn-v2-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-${{ runner.node_version}}-yarn-v4-
${{ runner.os }}-yarn-v2-
- name: Install dependencies
run: yarn install --frozen-lockfile --silent
env:

4
.gitignore vendored
View File

@@ -20,7 +20,3 @@ dist
**/.envrc
.blitz-*
.blitz-cli-cache
.vscode
.tsbuildinfo
.nvmrc
**/.test*

View File

@@ -1,5 +0,0 @@
# .kodiak.toml
# Minimal config. version is the only required field.
version = 1
merge.automerge_label = "0 - <(^_^)> - merge it! ✌️"
approve.auto_approve_usernames = ["flybayer", "depfu"]

View File

@@ -1 +0,0 @@
12.20.0

137
README.md
View File

@@ -6,7 +6,7 @@
<img alt="" src="https://img.shields.io/badge/Join%20our%20community-6700EB.svg?style=for-the-badge&labelColor=000000&logoWidth=20&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ9SURBVHgB7d3dVdtAEIbhcSpICUoH0IEogQqSVBBSAU4FSSpIOoAORAfQgSghHXzZ1U/YcMD4R9rZmf2ec3y448LyiNf27iLiGIAmPLrweC9Un3DhrzG6EarLNP09nlwJ1SOZ/lQr5N80/S/p2QMVCBf5N17XCfm1Y/rBHqjAG9PPHvBsz+mf9WAP+HLA9M/YA14cOP2payH7jpj+VCtk1wnTP+vj7xCy6cTpn7EHLMLp059iD1iD8eveJbVCNsSLheX1YA/YgOWnf8YeKB3Wmf7Ud6Fy4f/FHmtpxbl3YlC4MJ/Cj0bWdwPnPbARg+L0S54XQHS32WwuxClzd4CM0z9rPfeAuTtA5ulPXYQ7wZ04Y+oOoDD9KZc9YOoOoDj9s4dwFzgXR6w1wIPoOvPWA9buAHEJ173o3gWiy3AnuBUHLEbgmYwvAk1/wuM8vAgexThzbwPDkx7/DHwVXfFOxP2GmsKd4Ab6zPeAyU8CI7AHFmH2BRCBPXAyk18GzUrqAXCTiR4ssyj0VFw/oCU8+e+RZ33AWz6KMaYbIIWxB+JSLs1bsbkeMN0AqakHvoku9oA2sAfqBvbAQdw0QArsgb25aYBUQT3QgT2gB+yBuqGcHij2UCqXDZACe2Anlw2QYg/QAOyBuoE98CL3DZDCuK4/rh/Q7oGL6U+TOvcNkJoijN8X1C48+T+g75eQDrAH/qmqAVJgDwyqaoAUe4AGYA/UDZX3QLUNkEIZPRCd5+6BahsgVUgPROwBTSijB7jpVAvGHriHvmw9wAZ4BpX1ABvgmakHtPcbRuwBTWAPULgAV9D/jKDY9YRvwvgEaurD44uQHvAol7qBW7WKluVtIHiUS7GyvA0s6CiXDnxrpQfsgbqBS7GKk/2jYHCrVlGyfxTMrVo0ALdq1Q3sgSKofh0M9oA61a+D2QM0AHugbmAPqClmSRjK2apVVQ8UsySsoK1aHdgDesCtWnUDeyCrIpeFg1u3sylyWTi3btMA7IG6gT2wuuK3hoE9sKrit4YVslWLPaAN7IG6ocKt2zmY2h4O9sDiTG0PZw/QANy6XTewBxZj9ogYVHy025LMHhEz9cBn0We6B0yfERReBLfhx0/R1YQHPx/QBPbA0VwcEwf2wNFcHBPHHjiem3MC2QPHcXdSaJjA+KfgTPQ8hhfjBzHC40mhlzJ+Xq9lK4a4PCs43AVaGTed5mZq+iOXZwWHi3AnOj2wFWNcnxYe7gTxLtBKHuamP/J+Wnh8a5irB7ZC5Yk9gPX1QuXC+usHWqGyhYvUYR0a7zboUOFCNVhnk0krZAOW7wFOvzXhom2xnEbIHizTA1wEYhWW6YFGyC6c1gOcfg9wfA80Qj7g8B7g9HuCww+haIR8wf49wOn3Cvv9k8tGyC/s7gFOv3fY3QONkH+v9MBWqB7PeqDn9FcIT//kcitUn6kHOu/T/xfWzlQy3dEHhwAAAABJRU5ErkJggg==">
</a>
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-184-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
<a aria-label="All Contributors" href="#contributors-"><img alt="" src="https://img.shields.io/badge/all_contributors-122-17BB8A.svg?style=for-the-badge&labelColor=000000"></a>
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<a aria-label="License" href="https://github.com/blitz-js/blitz/blob/canary/LICENSE">
<img alt="" src="https://img.shields.io/npm/l/blitz.svg?style=for-the-badge&labelColor=000000&color=blue">
@@ -40,9 +40,7 @@ You need Node.js 12 or newer
#### Install Blitz
Run `npm install -g blitz` or `yarn global add blitz`
_You can alternatively use [`npx`](https://www.npmjs.com/package/npx)_
Run `npm install -g blitz`
#### Create a New App
@@ -53,12 +51,6 @@ _You can alternatively use [`npx`](https://www.npmjs.com/package/npx)_
<br><br>
<a aria-label="Bytes Newsletter" href="https://ui.dev/bytes/?r=blitzjs">
<img alt="Bytes Newsletter" src="https://files-8wtskjofb.vercel.app/smarter-16x1.jpg">
</a>
<br><br>
![Architecture diagram](https://blitzjs.now.sh/img/architecture-diagram.png)
<br><br>
@@ -124,21 +116,16 @@ Your financial contributions help ensure Blitz continues to be developed and mai
👉 View options and contribute at [GitHub Sponsors](https://github.com/sponsors/blitz-js), [PayPal](https://paypal.me/thebayers), or [Open Collective](https://opencollective.com/blitzjs)
### 🌱 Seedling Sponsors
<a aria-label="React Bricks" href="https://reactbricks.com/?utm_source=blitzjs&utm_medium=sponsorship&utm_campaign=blitzjs_sponsorship">
<img alt="" src="https://reactbricks.com/reactbricks_icon.svg" width="30px"/>
</a>
<a aria-label="Andreas Asprou" href="https://andreas.fyi">
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/andreas.jpg" width="30px"/>
<img alt="" src="https://reactbricks.com/reactbricks_icon.svg" width="30px">
</a>
### 🥉 Bronze Sponsors
<a aria-label="Render.com" href="https://render.com?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2020">
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/render-logo-color2.png" width="110px">
<a aria-label="Your Company" href="#">
<img alt="" src="https://dummyimage.com/1000x330/efe8ff/000000.png&text=Your+Logo+Here" width="100px">
</a>
### 🥈 Silver Sponsors
@@ -149,11 +136,9 @@ Your financial contributions help ensure Blitz continues to be developed and mai
### 🏆 Gold Sponsors
<div>
<a aria-label="G2i" href="http://g2i.co/sign-up?utm_source=blitz&utm_medium=referral&utm_campaign=blitz2020">
<img alt="" src="https://files-5oz00y7xp.vercel.app/G2i_Logo_wwords.png" width="160px">
<a aria-label="Your Company" href="#">
<img alt="" src="https://dummyimage.com/1000x330/efe8ff/000000.png&text=Your+Logo+Here" width="300px">
</a>
</div>
### 💎 Diamond Sponsors
@@ -169,6 +154,8 @@ Your financial contributions help ensure Blitz continues to be developed and mai
<table>
<tr>
<td align="center"><a href="https://twitter.com/flybayer"><img src="https://avatars3.githubusercontent.com/u/8813276?v=4" width="100px;" alt=""/><br /><sub><b>Brandon Bayer</b></sub></a><br />Creator</td>
<td align="center"><a href="https://medium.com/@ryardley"><img src="https://avatars0.githubusercontent.com/u/1256409?v=4" width="100px;" alt=""/><br /><sub><b>Rudi Yardley</b></sub></a><br />Node.js Wizard</td>
<td align="center"><a href="https://merelinguist.now.sh"><img src="https://avatars3.githubusercontent.com/u/24858006?v=4" width="100px;" alt=""/><br /><sub><b>Dylan Brookes</b></sub></a><br />Friendly Generalist</td>
</tr>
</table>
<!-- markdownlint-enable -->
@@ -187,7 +174,6 @@ _Code ownership, pull request approvals and merging, etc_ (see [MAINTAINERS.md](
<tr>
<td align="center"><a href="https://github.com/aem"><img src="https://avatars0.githubusercontent.com/u/1909883?v=4" width="100px;" alt=""/><br /><sub><b>Adam Markon</b></sub></a><br />CLI</td>
<td align="center"><a href="http://robdrosenberg.com"><img src="https://avatars0.githubusercontent.com/u/20813991?v=4" width="100px;" alt=""/><br /><sub><b>Robert Rosenberg</b></sub></a><br />Website/Docs</td>
<td align="center"><a href="http://simonknott.de"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4" width="100px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br />SuperJSON</td>
</tr>
</table>
<!-- markdownlint-enable -->
@@ -206,17 +192,15 @@ _Issue triage, pull request triage, community encouragement and moderation, etc_
<tr>
<td align="center"><a href="https://github.com/LoriKarikari"><img src="https://avatars1.githubusercontent.com/u/7902980?v=4" width="100px;" alt=""/><br /><sub><b>Lori Karikari</b></sub></a></td>
<td align="center"><a href="https://corey-brown.com"><img src="https://avatars1.githubusercontent.com/u/12791148?v=4" width="100px;" alt=""/><br /><sub><b>Corey Brown</b></sub></a></td>
<td align="center"><a href="http://simonknott.de"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4" width="100px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a></td>
<td align="center"><a href="http://jeremyliberman.com/"><img src="https://avatars3.githubusercontent.com/u/2754163?v=4" width="100px;" alt=""/><br /><sub><b>Jeremy Liberman</b></td>
<td align="center"><a href="http://jagascript.com"><img src="https://avatars0.githubusercontent.com/u/4562878?v=4" width="100px;" alt=""/><br /><sub><b>Jaga Santagostino</b></sub></a></td>
<td align="center"><a href="https://twitter.com/nitaking_"><img src="https://avatars2.githubusercontent.com/u/10850034?v=4" width="100px;" alt=""/><br /><sub><b>Satoshi Nitawaki</b></sub></a></td>
<td align="center"><a href="https://twitter.com/sandulat"><img src="https://avatars2.githubusercontent.com/u/7345874?v=4" width="100px;" alt=""/><br /><sub><b>Alexandru Stratulat</b></sub></a></td>
<td align="center"><a href="https://simonpeterdebbarma.com"><img src="https://avatars3.githubusercontent.com/u/31207418?v=4" width="100px;" alt=""/><br /><sub><b>Simon Debbarma</b></sub></a></td>
<td align="center"><a href="https://github.com/jclancy93"><img src="https://avatars2.githubusercontent.com/u/7850202?v=4" width="100px;" alt=""/><br /><sub><b>Jack Clancy</b></sub></a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/engelkes-finstreet"><img src="https://avatars0.githubusercontent.com/u/36962022?s=460&u=34cfc4a3d6da0a87026f6068c371779c68daa3a2&v=4" width="100px;" alt=""/><br /><sub><b>Patrick Engelkes</b></sub></a></td>
<td align="center"><a href="https://twitter.com/jdavenport97"><img src="https://avatars2.githubusercontent.com/u/1329874?v=4" width="100px;" alt=""/><br /><sub><b>Jamie Davenport</b></sub></a></td>
<td align="center"><a href="https://twitter.com/myrondavis"><img src="https://avatars2.githubusercontent.com/u/1430136?v=4" width="100px;" alt=""/><br /><sub><b>Myron Davis</b></sub></a></td>
<td align="center"><a href="https://flavioander.com/"><img src="https://avatars2.githubusercontent.com/u/14948074?s=460&u=31d7ea58b5c5cd9f724d684ed578f68896c4af71&v=4" width="100px;" alt=""/><br /><sub><b>Flavio Andrade</b></sub></a></td>
<td align="center"><a href="https://twitter.com/NaReto1125_"><img src="https://avatars.githubusercontent.com/reo777" width="100px;" alt=""/><br /><sub><b>Reo Ishiyama</b></sub></a></td>
<td align="center"><a href="https://twitter.com/ivandevp"><img src="https://avatars3.githubusercontent.com/u/9284690?v=4" width="100px;" alt=""/><br /><sub><b>Ivan Medina</b></sub></a></td>
<td align="center"><a href="https://twitter.com/nitaking_"><img src="https://avatars2.githubusercontent.com/u/10850034?v=4" width="100px;" alt=""/><br /><sub><b>Satoshi Nitawaki</b></sub></a></td>
</tr>
</table>
<!-- markdownlint-enable -->
@@ -276,7 +260,7 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
<td align="center"><a href="https://mikeattara.com"><img src="https://avatars1.githubusercontent.com/u/31483629?v=4" width="100px;" alt=""/><br /><sub><b>Mike Perry Y Attara</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mikeattara" title="Documentation">📖</a></td>
<td align="center"><a href="https://devanthe.dev"><img src="https://avatars0.githubusercontent.com/u/354652?v=4" width="100px;" alt=""/><br /><sub><b>Devan</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=DevanB" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/jclancy93"><img src="https://avatars2.githubusercontent.com/u/7850202?v=4" width="100px;" alt=""/><br /><sub><b>Jack Clancy</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jclancy93" title="Code">💻</a> <a href="#maintenance-jclancy93" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://github.com/ntgussoni"><img src="https://avatars0.githubusercontent.com/u/10161067?v=4" width="100px;" alt=""/><br /><sub><b>Nicolas Torres</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3Antgussoni" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/ntgussoni"><img src="https://avatars0.githubusercontent.com/u/10161067?v=4" width="100px;" alt=""/><br /><sub><b>Nicolas Torres</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=ntgussoni" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="http://simonknott.de"><img src="https://avatars1.githubusercontent.com/u/14912729?v=4" width="100px;" alt=""/><br /><sub><b>Simon Knott</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Skn0tt" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=Skn0tt" title="Tests">⚠️</a> <a href="#maintenance-Skn0tt" title="Maintenance">🚧</a></td>
@@ -311,7 +295,7 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
<td align="center"><a href="https://kevo.dev"><img src="https://avatars3.githubusercontent.com/u/15717067?v=4" width="100px;" alt=""/><br /><sub><b>Kevin Tovar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=kevotovar" title="Code">💻</a></td>
<td align="center"><a href="http://anteprimorac.com.hr"><img src="https://avatars0.githubusercontent.com/u/972083?v=4" width="100px;" alt=""/><br /><sub><b>Ante Primorac</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=anteprimorac" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=anteprimorac" title="Documentation">📖</a></td>
<td align="center"><a href="http://mykalmachon.dev"><img src="https://avatars1.githubusercontent.com/u/7844994?v=4" width="100px;" alt=""/><br /><sub><b>Mykal Machon</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=MykalMachon" title="Code">💻</a></td>
<td align="center"><a href="https://jamiedavenport.dev"><img src="https://avatars2.githubusercontent.com/u/1329874?v=4" width="100px;" alt=""/><br /><sub><b>Jamie Davenport</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jamiedavenport" title="Code">💻</a> <a href="#maintenance-jamiedavenport" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://jamiedavenport.dev"><img src="https://avatars2.githubusercontent.com/u/1329874?v=4" width="100px;" alt=""/><br /><sub><b>Jamie Davenport</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jamiedavenport" title="Code">💻</a></td>
<td align="center"><a href="https://cloudnweb.dev/"><img src="https://avatars0.githubusercontent.com/u/17050715?v=4" width="100px;" alt=""/><br /><sub><b>GaneshMani</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ganeshmani" title="Code">💻</a></td>
</tr>
<tr>
@@ -362,11 +346,11 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
<tr>
<td align="center"><a href="https://github.com/jschepmans"><img src="https://avatars2.githubusercontent.com/u/5782977?v=4" width="100px;" alt=""/><br /><sub><b>Johan Schepmans</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jschepmans" title="Code">💻</a></td>
<td align="center"><a href="https://twitter.com/dillonraphael"><img src="https://avatars0.githubusercontent.com/u/3496193?v=4" width="100px;" alt=""/><br /><sub><b>Dillon Raphael</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dillonraphael" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/clgeoio"><img src="https://avatars2.githubusercontent.com/u/37571416?v=4" width="100px;" alt=""/><br /><sub><b>Cody G</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=clgeoio" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=clgeoio" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/clgeoio"><img src="https://avatars2.githubusercontent.com/u/37571416?v=4" width="100px;" alt=""/><br /><sub><b>Cody G</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=clgeoio" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/madflow"><img src="https://avatars0.githubusercontent.com/u/183248?v=4" width="100px;" alt=""/><br /><sub><b>madflow</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=madflow" title="Documentation">📖</a></td>
<td align="center"><a href="https://twitter.com/nitaking_"><img src="https://avatars2.githubusercontent.com/u/10850034?v=4" width="100px;" alt=""/><br /><sub><b>Satoshi Nitawaki</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nitaking" title="Code">💻</a> <a href="#maintenance-nitaking" title="Maintenance">🚧</a> <a href="#question-nitaking" title="Answering Questions">💬</a> <a href="https://github.com/blitz-js/blitz/commits?author=nitaking" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/sirmyron"><img src="https://avatars2.githubusercontent.com/u/1430136?v=4" width="100px;" alt=""/><br /><sub><b>sirmyron</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sirmyron" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=sirmyron" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/engelkes-finstreet"><img src="https://avatars1.githubusercontent.com/u/36962022?v=4" width="100px;" alt=""/><br /><sub><b>engelkes-finstreet</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=engelkes-finstreet" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=engelkes-finstreet" title="Code">💻</a> <a href="#maintenance-engelkes-finstreet" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://twitter.com/nitaking_"><img src="https://avatars2.githubusercontent.com/u/10850034?v=4" width="100px;" alt=""/><br /><sub><b>Satoshi Nitawaki</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nitaking" title="Code">💻</a> <a href="#maintenance-nitaking" title="Maintenance">🚧</a> <a href="#question-nitaking" title="Answering Questions">💬</a></td>
<td align="center"><a href="https://github.com/sirmyron"><img src="https://avatars2.githubusercontent.com/u/1430136?v=4" width="100px;" alt=""/><br /><sub><b>sirmyron</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sirmyron" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/engelkes-finstreet"><img src="https://avatars1.githubusercontent.com/u/36962022?v=4" width="100px;" alt=""/><br /><sub><b>engelkes-finstreet</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=engelkes-finstreet" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=engelkes-finstreet" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="http://twitter.com/pixelscommander"><img src="https://avatars2.githubusercontent.com/u/810671?v=4" width="100px;" alt=""/><br /><sub><b>Denis Radin</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/pulls?q=is%3Apr+reviewed-by%3APixelsCommander" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/blitz-js/blitz/commits?author=PixelsCommander" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=PixelsCommander" title="Documentation">📖</a></td>
@@ -390,92 +374,11 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
<td align="center"><a href="http://enricoschaaf.com"><img src="https://avatars1.githubusercontent.com/u/54645197?v=4" width="100px;" alt=""/><br /><sub><b>Enrico Schaaf</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=enricoschaaf" title="Code">💻</a></td>
<td align="center"><a href="http://kitze.io"><img src="https://avatars0.githubusercontent.com/u/1160594?v=4" width="100px;" alt=""/><br /><sub><b>Kitze</b></sub></a><br /><a href="#ideas-kitze" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/drmas"><img src="https://avatars3.githubusercontent.com/u/644440?v=4" width="100px;" alt=""/><br /><sub><b>Mohamed Shaban</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=drmas" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/jorisre"><img src="https://avatars1.githubusercontent.com/u/7545547?v=4" width="100px;" alt=""/><br /><sub><b>Joris</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jorisre" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Kamshak"><img src="https://avatars3.githubusercontent.com/u/337968?v=4" width="100px;" alt=""/><br /><sub><b>Valentin Funk</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Kamshak" title="Documentation">📖</a></td>
<td align="center"><a href="https://lukebennett.com"><img src="https://avatars1.githubusercontent.com/u/135390?v=4" width="100px;" alt=""/><br /><sub><b>Luke Bennett</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=lukebennett" title="Code">💻</a></td>
<td align="center"><a href="https://haseebmajid.dev"><img src="https://avatars0.githubusercontent.com/u/998807?v=4" width="100px;" alt=""/><br /><sub><b>Haseeb Majid</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hmajid2301" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/phillippschmedt"><img src="https://avatars0.githubusercontent.com/u/16028406?v=4" width="100px;" alt=""/><br /><sub><b>Phillipp Schmedt</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=phillippschmedt" title="Code">💻</a></td>
<td align="center"><a href="https://haspar.us"><img src="https://avatars0.githubusercontent.com/u/15332326?v=4" width="100px;" alt=""/><br /><sub><b>Piotr Monwid-Olechnowicz</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=hasparus" title="Code">💻</a></td>
<td align="center"><a href="https://mizchi.dev"><img src="https://avatars2.githubusercontent.com/u/73962?v=4" width="100px;" alt=""/><br /><sub><b>Kotaro Chikuba</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mizchi" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=mizchi" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/konradkalemba"><img src="https://avatars0.githubusercontent.com/u/8682104?v=4" width="100px;" alt=""/><br /><sub><b>Konrad Kalemba</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=konradkalemba" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Alucard17"><img src="https://avatars1.githubusercontent.com/u/26205172?v=4" width="100px;" alt=""/><br /><sub><b>Alucard17</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Alucard17" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Dohxis"><img src="https://avatars2.githubusercontent.com/u/8768909?v=4" width="100px;" alt=""/><br /><sub><b>Domantas Mauruča</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Dohxis" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=Dohxis" title="Code">💻</a></td>
<td align="center"><a href="https://sandulat.com/"><img src="https://avatars0.githubusercontent.com/u/7345874?v=4" width="100px;" alt=""/><br /><sub><b>Stratulat Alexandru</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sandulat" title="Code">💻</a> <a href="#maintenance-sandulat" title="Maintenance">🚧</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/aericson"><img src="https://avatars3.githubusercontent.com/u/692542?v=4" width="100px;" alt=""/><br /><sub><b>André Ericson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aericson" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=aericson" title="Documentation">📖</a></td>
<td align="center"><a href="http://Cajotafer.com"><img src="https://avatars2.githubusercontent.com/u/41461969?v=4" width="100px;" alt=""/><br /><sub><b>Carlos Fernández</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cajotafer" title="Documentation">📖</a></td>
<td align="center"><a href="https://oesterkilde.dk/"><img src="https://avatars1.githubusercontent.com/u/6379824?v=4" width="100px;" alt=""/><br /><sub><b>Kevin Østerkilde</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Kosai106" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=Kosai106" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/aaronfulkerson"><img src="https://avatars0.githubusercontent.com/u/31112737?v=4" width="100px;" alt=""/><br /><sub><b>aaronfulkerson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=aaronfulkerson" title="Code">💻</a> <a href="#question-aaronfulkerson" title="Answering Questions">💬</a></td>
<td align="center"><a href="https://github.com/alexnaiman"><img src="https://avatars3.githubusercontent.com/u/25799714?v=4" width="100px;" alt=""/><br /><sub><b>Alexandru Naiman</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=alexnaiman" title="Code">💻</a></td>
<td align="center"><a href="https://davidlutta.github.io/portfolio/"><img src="https://avatars2.githubusercontent.com/u/14890315?v=4" width="100px;" alt=""/><br /><sub><b>David Ezekiel Lutta</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=davidlutta" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/wanjuntham"><img src="https://avatars1.githubusercontent.com/u/49380551?v=4" width="100px;" alt=""/><br /><sub><b>wanjuntham</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=wanjuntham" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="http://www.nahuelchaves.xyz"><img src="https://avatars3.githubusercontent.com/u/96837?v=4" width="100px;" alt=""/><br /><sub><b>Victor Nahuel Chaves</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nahue" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/peter50216"><img src="https://avatars3.githubusercontent.com/u/891109?v=4" width="100px;" alt=""/><br /><sub><b>Peter Shih</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=peter50216" title="Code">💻</a></td>
<td align="center"><a href="http://seweryn.kale.mba"><img src="https://avatars3.githubusercontent.com/u/37031328?v=4" width="100px;" alt=""/><br /><sub><b>Seweryn Kalemba</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sewerynkalemba" title="Code">💻</a></td>
<td align="center"><a href="https://nksaraf.github.io"><img src="https://avatars2.githubusercontent.com/u/11255148?v=4" width="100px;" alt=""/><br /><sub><b>Nikhil Saraf</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nksaraf" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=nksaraf" title="Documentation">📖</a></td>
<td align="center"><a href="https://zane.sh"><img src="https://avatars0.githubusercontent.com/u/16865690?v=4" width="100px;" alt=""/><br /><sub><b>Zane</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=zanedb" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/dulcehc"><img src="https://avatars1.githubusercontent.com/u/19391835?v=4" width="100px;" alt=""/><br /><sub><b>Dulce Hernández</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dulcehc" title="Code">💻</a></td>
<td align="center"><a href="https://markhaehnel.de"><img src="https://avatars2.githubusercontent.com/u/1516205?v=4" width="100px;" alt=""/><br /><sub><b>Mark Hähnel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=markhaehnel" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="http://stackoverflow.com/users/872395/nemesv"><img src="https://avatars0.githubusercontent.com/u/251330?v=4" width="100px;" alt=""/><br /><sub><b>Viktor Nemes</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=nemesv" title="Code">💻</a></td>
<td align="center"><a href="http://gabeoleary.com"><img src="https://avatars1.githubusercontent.com/u/16123225?v=4" width="100px;" alt=""/><br /><sub><b>Gabe O'Leary</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=goleary" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/machadolucasvp"><img src="https://avatars0.githubusercontent.com/u/44952113?v=4" width="100px;" alt=""/><br /><sub><b>Lucas Machado</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=machadolucasvp" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/maciekgrzybek"><img src="https://avatars2.githubusercontent.com/u/16546428?v=4" width="100px;" alt=""/><br /><sub><b>maciek_grzybek</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=maciekgrzybek" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/mweibel"><img src="https://avatars1.githubusercontent.com/u/307427?v=4" width="100px;" alt=""/><br /><sub><b>Michael Weibel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mweibel" title="Code">💻</a></td>
<td align="center"><a href="https://isoppp.com"><img src="https://avatars0.githubusercontent.com/u/16318727?v=4" width="100px;" alt=""/><br /><sub><b>Hiroki Isogai</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=isoppp" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/matamatanot"><img src="https://avatars2.githubusercontent.com/u/39780486?v=4" width="100px;" alt=""/><br /><sub><b>matamatanot</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=matamatanot" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/ericsakmar"><img src="https://avatars3.githubusercontent.com/u/5620709?v=4" width="100px;" alt=""/><br /><sub><b>Eric Sakmar</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ericsakmar" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/leggsimon"><img src="https://avatars2.githubusercontent.com/u/11544418?v=4" width="100px;" alt=""/><br /><sub><b>Simon Legg</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=leggsimon" title="Documentation">📖</a></td>
<td align="center"><a href="https://robsoriano.com"><img src="https://avatars3.githubusercontent.com/u/13049130?v=4" width="100px;" alt=""/><br /><sub><b>Robert Soriano</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=wobsoriano" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/benediktms"><img src="https://avatars2.githubusercontent.com/u/48836135?v=4" width="100px;" alt=""/><br /><sub><b>Benedikt Schnatterbeck</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=benediktms" title="Code">💻</a></td>
<td align="center"><a href="http://taloranderson.com"><img src="https://avatars2.githubusercontent.com/u/11509865?v=4" width="100px;" alt=""/><br /><sub><b>Talor Anderson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Talor-A" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/akirabaruah"><img src="https://avatars2.githubusercontent.com/u/6751517?v=4" width="100px;" alt=""/><br /><sub><b>Akira Baruah</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=akirabaruah" title="Code">💻</a></td>
<td align="center"><a href="https://chriswray.dev/"><img src="https://avatars0.githubusercontent.com/u/53663762?v=4" width="100px;" alt=""/><br /><sub><b>Christopher Wray</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=cwray-tech" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/piotrski"><img src="https://avatars0.githubusercontent.com/u/244174?v=4" width="100px;" alt=""/><br /><sub><b>Piotrek Tomczewski</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=piotrski" title="Code">💻</a></td>
<td align="center"><a href="http://raph.site"><img src="https://avatars3.githubusercontent.com/u/1575946?v=4" width="100px;" alt=""/><br /><sub><b>Raphaël Huchet</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Documentation">📖</a> <a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Tests">⚠️</a> <a href="https://github.com/blitz-js/blitz/commits?author=rap2hpoutre" title="Code">💻</a></td>
<td align="center"><a href="http://kattcorp.com"><img src="https://avatars1.githubusercontent.com/u/459267?v=4" width="100px;" alt=""/><br /><sub><b>Alex Johansson</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=KATT" title="Code">💻</a></td>
<td align="center"><a href="http://davidmazza.com"><img src="https://avatars0.githubusercontent.com/u/120893?v=4" width="100px;" alt=""/><br /><sub><b>David Mazza</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dmzza" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/rayandrews"><img src="https://avatars1.githubusercontent.com/u/4437323?v=4" width="100px;" alt=""/><br /><sub><b>Ray Andrew</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=rayandrews" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=rayandrews" title="Documentation">📖</a></td>
<td align="center"><a href="http://Dal.Design"><img src="https://avatars3.githubusercontent.com/u/43112535?v=4" width="100px;" alt=""/><br /><sub><b>Abdullah Mzaien</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Mzaien" title="Code">💻</a></td>
<td align="center"><a href="http://kwao.io"><img src="https://avatars2.githubusercontent.com/u/8839514?v=4" width="100px;" alt=""/><br /><sub><b>William Kwao</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=williamkwao" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/sakulstra"><img src="https://avatars3.githubusercontent.com/u/4396533?v=4" width="100px;" alt=""/><br /><sub><b>Lukas Strassel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=sakulstra" title="Code">💻</a></td>
<td align="center"><a href="https://thibpat.com"><img src="https://avatars3.githubusercontent.com/u/494686?v=4" width="100px;" alt=""/><br /><sub><b>Thibaut Patel</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tpatel" title="Code">💻</a></td>
<td align="center"><a href="http://jonstuebe.com"><img src="https://avatars0.githubusercontent.com/u/156722?v=4" width="100px;" alt=""/><br /><sub><b>Jon Stuebe</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jonstuebe" title="Code">💻</a></td>
<td align="center"><a href="https://ugogo.dev"><img src="https://avatars2.githubusercontent.com/u/5040476?v=4" width="100px;" alt=""/><br /><sub><b>Ugo Onali</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ugogo" title="Documentation">📖</a></td>
<td align="center"><a href="https://saintmalik.me"><img src="https://avatars1.githubusercontent.com/u/37118134?v=4" width="100px;" alt=""/><br /><sub><b>SaintMalik</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=saintmalik" title="Documentation">📖</a></td>
<td align="center"><a href="https://khaledgarbaya.net"><img src="https://avatars1.githubusercontent.com/u/1156093?v=4" width="100px;" alt=""/><br /><sub><b>Khaled Garbaya</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Khaledgarbaya" title="Code">💻</a></td>
<td align="center"><a href="https://tundera.dev"><img src="https://avatars0.githubusercontent.com/u/61833561?v=4" width="100px;" alt=""/><br /><sub><b>tundera</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=tundera" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/markylaing"><img src="https://avatars2.githubusercontent.com/u/41469221?v=4" width="100px;" alt=""/><br /><sub><b>markylaing</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=markylaing" title="Code">💻</a> <a href="https://github.com/blitz-js/blitz/commits?author=markylaing" title="Documentation">📖</a></td>
<td align="center"><a href="https://akfm.dev/"><img src="https://avatars2.githubusercontent.com/u/25711332?v=4" width="100px;" alt=""/><br /><sub><b>Akifumi Sato</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=AkifumiSato" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/beeplin"><img src="https://avatars3.githubusercontent.com/u/13058150?v=4" width="100px;" alt=""/><br /><sub><b>Beep LIN</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=beeplin" title="Code">💻</a></td>
<td align="center"><a href="https://mattwood.tech/"><img src="https://avatars1.githubusercontent.com/u/22530815?v=4" width="100px;" alt=""/><br /><sub><b>Matt Wood</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=mattfwood" title="Code">💻</a></td>
<td align="center"><a href="http://joaquin.axai.mx"><img src="https://avatars1.githubusercontent.com/u/15214?v=4" width="100px;" alt=""/><br /><sub><b>Joaquin Bravo Contreras</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=jackbravo" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/arjundubey-cr"><img src="https://avatars0.githubusercontent.com/u/40758425?v=4" width="100px;" alt=""/><br /><sub><b>Arjun Dubey</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=arjundubey-cr" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/chanand"><img src="https://avatars0.githubusercontent.com/u/1317789?v=4" width="100px;" alt=""/><br /><sub><b>chanand</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=chanand" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/phillipkregg"><img src="https://avatars0.githubusercontent.com/u/1066044?v=4" width="100px;" alt=""/><br /><sub><b>phillipkregg</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=phillipkregg" title="Documentation">📖</a></td>
<td align="center"><a href="http://timothyreynolds.co.uk"><img src="https://avatars1.githubusercontent.com/u/168870?v=4" width="100px;" alt=""/><br /><sub><b>Tim Reynolds</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=timReynolds" title="Documentation">📖</a></td>
</tr>
</table>
<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -23,115 +23,109 @@ assert(process.env.AUTH0_CLIENT_SECRET, "You must provide the AUTH0_CLIENT_SECRE
export default passportAuth({
successRedirectUrl: "/",
authenticateOptions: {scope: "openid email profile"}, //used for Auth0Strategy - without an empty profile is returned
strategies: [
{
strategy: new TwitterStrategy(
{
consumerKey: process.env.TWITTER_CONSUMER_KEY,
consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
callbackURL:
process.env.NODE_ENV === "production"
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/twitter/callback"
: "http://localhost:3000/api/auth/twitter/callback",
includeEmail: true,
},
async function (_token, _tokenSecret, profile, done) {
const email = profile.emails && profile.emails[0]?.value
new TwitterStrategy(
{
consumerKey: process.env.TWITTER_CONSUMER_KEY,
consumerSecret: process.env.TWITTER_CONSUMER_SECRET,
callbackURL:
process.env.NODE_ENV === "production"
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/twitter/callback"
: "http://localhost:3000/api/auth/twitter/callback",
includeEmail: true,
},
async function (_token, _tokenSecret, profile, done) {
const email = profile.emails && profile.emails[0]?.value
if (!email) {
// This can happen if you haven't enabled email access in your twitter app permissions
return done(new Error("Twitter OAuth response doesn't have email."))
}
if (!email) {
// This can happen if you haven't enabled email access in your twitter app permissions
return done(new Error("Twitter OAuth response doesn't have email."))
}
const user = await db.user.upsert({
where: {email},
create: {
email,
name: profile.displayName,
},
update: {email},
})
const user = await db.user.upsert({
where: {email},
create: {
email,
name: profile.displayName,
},
update: {email},
})
const publicData = {userId: user.id, roles: [user.role], source: "twitter"}
done(null, {publicData})
},
),
},
{
strategy: new GitHubStrategy(
{
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL:
process.env.NODE_ENV === "production"
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/github/callback"
: "http://localhost:3000/api/auth/github/callback",
},
async function (_token, _tokenSecret, profile, done) {
const email = profile.emails && profile.emails[0]?.value
const publicData = {userId: user.id, roles: [user.role], source: "twitter"}
done(null, {publicData})
},
),
new GitHubStrategy(
{
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL:
process.env.NODE_ENV === "production"
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/github/callback"
: "http://localhost:3000/api/auth/github/callback",
},
async function (_token, _tokenSecret, profile, done) {
const email = profile.emails && profile.emails[0]?.value
if (!email) {
// This can happen if you haven't enabled email access in your twitter app permissions
return done(new Error("Twitter OAuth response doesn't have email."))
}
if (!email) {
// This can happen if you haven't enabled email access in your twitter app permissions
return done(new Error("Twitter OAuth response doesn't have email."))
}
const user = await db.user.upsert({
where: {email},
create: {
email,
name: profile.displayName,
},
update: {email},
})
const user = await db.user.upsert({
where: {email},
create: {
email,
name: profile.displayName,
},
update: {email},
})
const publicData = {
userId: user.id,
roles: [user.role],
source: "github",
githubUsername: profile.username,
}
done(null, {publicData})
},
),
},
{
authenticateOptions: {scope: "openid email profile"},
strategy: new Auth0Strategy(
{
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
callbackURL:
process.env.NODE_ENV === "production"
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/auth0/callback"
: "http://localhost:3000/api/auth/auth0/callback",
},
async function (_token, _tokenSecret, extraParams, profile, done) {
const email = profile.emails && profile.emails[0]?.value
const publicData = {
userId: user.id,
roles: [user.role],
source: "github",
githubUsername: profile.username,
}
done(null, {publicData})
},
),
new Auth0Strategy(
{
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
callbackURL:
process.env.NODE_ENV === "production"
? "https://auth-example-flybayer.blitzjs.vercel.app/api/auth/auth0/callback"
: "http://localhost:3000/api/auth/auth0/callback",
},
async function (_token, _tokenSecret, extraParams, profile, done) {
const email = profile.emails && profile.emails[0]?.value
if (!email) {
// This can happen if you haven't enabled email access in your twitter app permissions
return done(new Error("Auth0 response doesn't have email."))
}
if (!email) {
// This can happen if you haven't enabled email access in your twitter app permissions
return done(new Error("GitHub OAuth response doesn't have email."))
}
const user = await db.user.upsert({
where: {email},
create: {
email,
name: profile.displayName,
},
update: {email},
})
const user = await db.user.upsert({
where: {email},
create: {
email,
name: profile.displayName,
},
update: {email},
})
const publicData = {
userId: user.id,
roles: [user.role],
source: "auth0",
githubUsername: profile.username,
}
done(undefined, {publicData})
},
),
},
const publicData = {
userId: user.id,
roles: [user.role],
source: "auth0",
githubUsername: profile.username,
}
done(undefined, {publicData})
},
),
],
})

View File

@@ -2,15 +2,15 @@ import {AuthenticationError} from "blitz"
import SecurePassword from "secure-password"
import db from "db"
const SP = () => new SecurePassword()
const SP = new SecurePassword()
export const hashPassword = async (password: string) => {
const hashedBuffer = await SP().hash(Buffer.from(password))
const hashedBuffer = await SP.hash(Buffer.from(password))
return hashedBuffer.toString("base64")
}
export const verifyPassword = async (hashedPassword: string, password: string) => {
try {
return await SP().verify(Buffer.from(password), Buffer.from(hashedPassword, "base64"))
return await SP.verify(Buffer.from(password), Buffer.from(hashedPassword, "base64"))
} catch (error) {
console.error(error)
return false
@@ -18,7 +18,7 @@ export const verifyPassword = async (hashedPassword: string, password: string) =
}
export const authenticateUser = async (email: string, password: string) => {
const user = await db().user.findFirst({where: {email}})
const user = await db.user.findOne({where: {email}})
if (!user || !user.hashedPassword) throw new AuthenticationError()
@@ -28,7 +28,7 @@ export const authenticateUser = async (email: string, password: string) => {
case SecurePassword.VALID_NEEDS_REHASH:
// Upgrade hashed password with a more secure hash
const improvedHash = await hashPassword(password)
await db().user.update({where: {id: user.id}, data: {hashedPassword: improvedHash}})
await db.user.update({where: {id: user.id}, data: {hashedPassword: improvedHash}})
break
default:
throw new AuthenticationError()

View File

@@ -1,27 +1,28 @@
import {Link, useMutation, AuthenticationError} from "blitz"
import React from "react"
import {Link} from "blitz"
import {LabeledTextField} from "app/components/LabeledTextField"
import {Form, FORM_ERROR} from "app/components/Form"
import login, {LoginInput} from "app/auth/mutations/login"
import login from "app/auth/mutations/login"
import {LoginInput} from "app/auth/validations"
type LoginFormProps = {
onSuccess?: () => void
}
export const LoginForm = (props: LoginFormProps) => {
const [loginMutation] = useMutation(login)
return (
<div>
<h1>Login</h1>
<Form
submitText="Login"
submitText="Log In"
schema={LoginInput}
initialValues={{email: undefined, password: undefined}}
onSubmit={async (values) => {
try {
await loginMutation(values)
await login({email: values.email, password: values.password})
props.onSuccess && props.onSuccess()
} catch (error) {
if (error instanceof AuthenticationError) {
if (error.name === "AuthenticationError") {
return {[FORM_ERROR]: "Sorry, those credentials are invalid"}
} else {
return {

View File

@@ -1,21 +1,15 @@
import {Ctx} from "blitz"
import {SessionContext} from "blitz"
import {authenticateUser} from "app/auth/auth-utils"
import * as z from "zod"
import {LoginInput, LoginInputType} from "../validations"
export const LoginInput = z.object({
email: z.string().email(),
password: z.string(),
})
export type LoginInputType = z.infer<typeof LoginInput>
export default async function login(input: LoginInputType, {session}: Ctx) {
export default async function login(input: LoginInputType, ctx: {session?: SessionContext} = {}) {
// This throws an error if input is invalid
const {email, password} = LoginInput.parse(input)
// This throws an error if credentials are invalid
const user = await authenticateUser(email, password)
await session.create({userId: user.id, roles: [user.role]})
await ctx.session!.create({userId: user.id, roles: [user.role]})
return user
}

View File

@@ -1,5 +1,5 @@
import {Ctx} from "blitz"
import {SessionContext} from "blitz"
export default async function logout(_: any, {session}: Ctx) {
return await session.revoke()
export default async function logout(_ = null, ctx: {session?: SessionContext} = {}) {
return await ctx.session!.revoke()
}

View File

@@ -1,25 +1,19 @@
import {Ctx} from "blitz"
import db from "db"
import {SessionContext} from "blitz"
import {hashPassword} from "app/auth/auth-utils"
import * as z from "zod"
import {SignupInput, SignupInputType} from "app/auth/validations"
export const SignupInput = z.object({
email: z.string().email(),
password: z.string().min(10).max(100),
})
export type SignupInputType = z.infer<typeof SignupInput>
export default async function signup(input: SignupInputType, {session}: Ctx) {
export default async function signup(input: SignupInputType, ctx: {session?: SessionContext} = {}) {
// This throws an error if input is invalid
const {email, password} = SignupInput.parse(input)
const hashedPassword = await hashPassword(password)
const user = await db().user.create({
const user = await db.user.create({
data: {email, hashedPassword, role: "user"},
select: {id: true, name: true, email: true, role: true},
})
await session.create({userId: user.id, roles: [user.role]})
await ctx.session!.create({userId: user.id, roles: [user.role]})
return user
}

View File

@@ -1,3 +1,4 @@
import React from "react"
import {Head, useRouter, BlitzPage} from "blitz"
import {LoginForm} from "app/auth/components/LoginForm"
@@ -7,7 +8,7 @@ const SignupPage: BlitzPage = () => {
return (
<>
<Head>
<title>Login</title>
<title>Log In</title>
<link rel="icon" href="/favicon.ico" />
</Head>

View File

@@ -1,11 +1,12 @@
import {Head, useRouter, BlitzPage, useMutation} from "blitz"
import React from "react"
import {Head, useRouter, BlitzPage} from "blitz"
import {Form, FORM_ERROR} from "app/components/Form"
import {LabeledTextField} from "app/components/LabeledTextField"
import signup, {SignupInput} from "app/auth/mutations/signup"
import signup from "app/auth/mutations/signup"
import {SignupInput} from "app/auth/validations"
const SignupPage: BlitzPage = () => {
const router = useRouter()
const [signupMutation] = useMutation(signup)
return (
<>
@@ -22,7 +23,7 @@ const SignupPage: BlitzPage = () => {
schema={SignupInput}
onSubmit={async (values) => {
try {
await signupMutation(values)
await signup({email: values.email, password: values.password})
router.push("/")
} catch (error) {
if (error.code === "P2002" && error.meta?.target?.includes("email")) {

View File

@@ -1,4 +1,4 @@
import {ReactNode, PropsWithoutRef} from "react"
import React, {ReactNode, PropsWithoutRef} from "react"
import {Form as FinalForm, FormProps as FinalFormProps} from "react-final-form"
import * as z from "zod"
export {FORM_ERROR} from "final-form"

View File

@@ -1,4 +1,4 @@
import {forwardRef, PropsWithoutRef} from "react"
import React, {PropsWithoutRef} from "react"
import {useField} from "react-final-form"
export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["input"]> {
@@ -11,7 +11,7 @@ export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElem
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
}
export const LabeledTextField = forwardRef<HTMLInputElement, LabeledTextFieldProps>(
export const LabeledTextField = React.forwardRef<HTMLInputElement, LabeledTextFieldProps>(
({name, label, outerProps, ...props}, ref) => {
const {
input,

View File

@@ -1,7 +1,10 @@
import {useQuery} from "blitz"
import {useQuery, useSession} from "blitz"
import getCurrentUser from "app/users/queries/getCurrentUser"
export const useCurrentUser = () => {
const [user] = useQuery(getCurrentUser, null)
return user
// We wouldn't have to useSession() here, but doing so improves perf on initial
// load since we can skip the getCurrentUser() request.
const session = useSession()
const [user] = useQuery(getCurrentUser, null, {enabled: !!session.userId})
return session.userId ? user : null
}

View File

@@ -1,17 +1,16 @@
import {useSession, useRouter, useMutation} from "blitz"
import {useSession, useRouter} from "blitz"
import logout from "app/auth/mutations/logout"
export default function Layout({children}: {children: React.ReactNode}) {
const session = useSession()
const router = useRouter()
const [logoutMutation] = useMutation(logout)
return (
<div>
{session.userId && (
<button
onClick={async () => {
router.push("/")
await logoutMutation()
await logout()
}}
>
Logout

View File

@@ -1,12 +1,8 @@
import {AppProps, ErrorComponent, useRouter, AuthenticationError, AuthorizationError} from "blitz"
import {AppProps, ErrorComponent, useRouter} from "blitz"
import {ErrorBoundary} from "react-error-boundary"
import {queryCache} from "react-query"
import LoginForm from "app/auth/components/LoginForm"
if (typeof window !== "undefined") {
window["DEBUG_BLITZ"] = 1
}
export default function App({Component, pageProps}: AppProps) {
const router = useRouter()
return (
@@ -25,9 +21,9 @@ export default function App({Component, pageProps}: AppProps) {
}
function RootErrorFallback({error, resetErrorBoundary}) {
if (error instanceof AuthenticationError) {
if (error.name === "AuthenticationError") {
return <LoginForm onSuccess={resetErrorBoundary} />
} else if (error instanceof AuthorizationError) {
} else if (error.name === "AuthorizationError") {
return (
<ErrorComponent
statusCode={error.statusCode}

View File

@@ -1,20 +0,0 @@
import {render} from "test/utils"
import Home from "./index"
import {useCurrentUser} from "app/hooks/useCurrentUser"
jest.mock("app/hooks/useCurrentUser")
const mockUseCurrentUser = useCurrentUser as jest.MockedFunction<typeof useCurrentUser>
test("renders blitz documentation link", () => {
mockUseCurrentUser.mockReturnValue({
id: 1,
name: "User",
email: "user@email.com",
role: "user",
})
const {getByText} = render(<Home />)
const element = getByText(/powered by blitz/i)
expect(element).toBeInTheDocument()
})

View File

@@ -1,10 +1,9 @@
import {Suspense} from "react"
import {Head, Link, useSession, useRouterQuery, useMutation, invoke} from "blitz"
import {Head, Link, useSession, useRouterQuery} from "blitz"
import getUser from "app/users/queries/getUser"
import trackView from "app/users/mutations/trackView"
import Layout from "app/layouts/Layout"
import {useCurrentUser} from "app/hooks/useCurrentUser"
// import getUsers from "app/users/queries/getUsers"
const CurrentUserInfo = () => {
const currentUser = useCurrentUser()
@@ -12,16 +11,9 @@ const CurrentUserInfo = () => {
return <pre>{JSON.stringify(currentUser, null, 2)}</pre>
}
// const Users = () => {
// const [users] = useQuery(getUsers, {})
//
// return <pre style={{maxWidth: "30rem"}}>{JSON.stringify(users, null, 2)}</pre>
// }
const UserStuff = () => {
const session = useSession()
const query = useRouterQuery()
const [trackViewMutation] = useMutation(trackView)
if (session.isLoading) return <div>Loading...</div>
@@ -33,7 +25,7 @@ const UserStuff = () => {
<Link href="/signup">Sign Up</Link>
</div>
<div>
<Link href="/login">Login</Link>
<Link href="/login">Log In</Link>
</div>
<a href="/api/auth/twitter" style={{display: "block"}}>
Login with Twitter
@@ -48,15 +40,10 @@ const UserStuff = () => {
<Suspense fallback="Loading...">
<CurrentUserInfo />
</Suspense>
{/*
<Suspense fallback="Loading...">
<Users />
</Suspense>
*/}
<button
onClick={async () => {
try {
const user = await invoke(getUser, {where: {id: session.userId as number}})
const user = await getUser({where: {id: session.userId as number}})
alert(JSON.stringify(user))
} catch (error) {
alert("error: " + JSON.stringify(error))
@@ -68,7 +55,7 @@ const UserStuff = () => {
<button
onClick={async () => {
try {
await trackViewMutation()
await trackView()
} catch (error) {
alert("error: " + error)
console.log(error)

View File

@@ -1,14 +1,11 @@
import {FC} from "react"
import * as React from "react"
import {getSessionContext} from "@blitzjs/server"
import {
invokeWithMiddleware,
ssrQuery,
useRouter,
GetServerSideProps,
PromiseReturnType,
ErrorComponent as ErrorPage,
useMutation,
AuthenticationError,
AuthorizationError,
} from "blitz"
import getUser from "app/users/queries/getUser"
import logout from "app/auth/mutations/logout"
@@ -33,9 +30,9 @@ export const getServerSideProps: GetServerSideProps<PageProps> = async ({req, re
const session = await getSessionContext(req, res)
console.log("Session id:", session.userId)
try {
const user = await invokeWithMiddleware(
const user = await ssrQuery(
getUser,
{where: {id: Number(session.userId)}},
{where: {id: Number(session.userId)}, select: {id: true}},
{res, req},
)
return {props: {user}}
@@ -44,10 +41,11 @@ export const getServerSideProps: GetServerSideProps<PageProps> = async ({req, re
res.statusCode = 404
res.end()
return {props: {}}
} else if (error instanceof AuthenticationError) {
res.writeHead(302, {location: "/login"}).end()
} else if (error.name === "AuthenticationError") {
res.writeHead(302, {location: "/login"})
res.end()
return {props: {}}
} else if (error instanceof AuthorizationError) {
} else if (error.name === "AuthorizationError") {
return {
props: {
error: {
@@ -62,9 +60,8 @@ export const getServerSideProps: GetServerSideProps<PageProps> = async ({req, re
}
}
const Test: FC<PageProps> = ({user, error}: PageProps) => {
const Test: React.FC<PageProps> = ({user, error}: PageProps) => {
const router = useRouter()
const [logoutMutation] = useMutation(logout)
if (error) {
return <ErrorPage statusCode={error.statusCode} title={error.message} />
@@ -75,7 +72,7 @@ const Test: FC<PageProps> = ({user, error}: PageProps) => {
<div>Logged in user id: {user?.id}</div>
<button
onClick={async () => {
await logoutMutation()
await logout()
router.push("/")
}}
>

View File

@@ -0,0 +1,23 @@
import React from "react"
type SessionFormProps = {
initialValues: any
onSubmit: React.FormEventHandler<HTMLFormElement>
}
const SessionForm = ({initialValues, onSubmit}: SessionFormProps) => {
return (
<form
onSubmit={(event) => {
event.preventDefault()
onSubmit(event)
}}
>
<div>Put your form fields here. But for now, just click submit</div>
<div>{JSON.stringify(initialValues)}</div>
<button>Submit</button>
</form>
)
}
export default SessionForm

View File

@@ -0,0 +1,16 @@
import {SessionContext} from "blitz"
import db, {SessionCreateArgs} from "db"
type CreateSessionInput = {
data: SessionCreateArgs["data"]
}
export default async function createSession(
{data}: CreateSessionInput,
ctx: {session?: SessionContext} = {},
) {
ctx.session!.authorize()
const session = await db.session.create({data})
return session
}

View File

@@ -0,0 +1,17 @@
import {SessionContext} from "blitz"
import db, {SessionDeleteArgs} from "db"
type DeleteSessionInput = {
where: SessionDeleteArgs["where"]
}
export default async function deleteSession(
{where}: DeleteSessionInput,
ctx: {session?: SessionContext} = {},
) {
ctx.session!.authorize()
const session = await db.session.delete({where})
return session
}

View File

@@ -0,0 +1,18 @@
import {SessionContext} from "blitz"
import db, {SessionUpdateArgs} from "db"
type UpdateSessionInput = {
where: SessionUpdateArgs["where"]
data: SessionUpdateArgs["data"]
}
export default async function updateSession(
{where, data}: UpdateSessionInput,
ctx: {session?: SessionContext} = {},
) {
ctx.session!.authorize()
const session = await db.session.update({where, data})
return session
}

View File

@@ -0,0 +1,60 @@
import React, {Suspense} from "react"
import Layout from "app/layouts/Layout"
import {Head, Link, useRouter, useQuery, useParam, BlitzPage} from "blitz"
import getSession from "app/sessions/queries/getSession"
import deleteSession from "app/sessions/mutations/deleteSession"
export const Session = () => {
const router = useRouter()
const sessionId = useParam("sessionId", "number")
const [session] = useQuery(getSession, {where: {id: sessionId}})
return (
<div>
<h1>Session {session.id}</h1>
<pre>{JSON.stringify(session, null, 2)}</pre>
<Link href="/sessions/[sessionId]/edit" as={`/sessions/${session.id}/edit`}>
<a>Edit</a>
</Link>
<button
type="button"
onClick={async () => {
if (window.confirm("This will be deleted")) {
await deleteSession({where: {id: session.id}})
router.push("/sessions")
}
}}
>
Delete
</button>
</div>
)
}
const ShowSessionPage: BlitzPage = () => {
return (
<div>
<Head>
<title>Session</title>
</Head>
<main>
<p>
<Link href="/sessions">
<a>Sessions</a>
</Link>
</p>
<Suspense fallback={<div>Loading...</div>}>
<Session />
</Suspense>
</main>
</div>
)
}
ShowSessionPage.getLayout = (page) => <Layout>{page}</Layout>
export default ShowSessionPage

View File

@@ -0,0 +1,68 @@
import React, {Suspense} from "react"
import Layout from "app/layouts/Layout"
import {Head, Link, usePaginatedQuery, useRouter, BlitzPage} from "blitz"
import getSessions from "app/sessions/queries/getSessions"
const ITEMS_PER_PAGE = 100
export const SessionsList = () => {
const router = useRouter()
const page = Number(router.query.page) || 0
const [{sessions, hasMore}] = usePaginatedQuery(getSessions, {
orderBy: {id: "asc"},
skip: ITEMS_PER_PAGE * page,
take: ITEMS_PER_PAGE,
})
const goToPreviousPage = () => router.push({query: {page: page - 1}})
const goToNextPage = () => router.push({query: {page: page + 1}})
return (
<div>
<ul>
{sessions.map((session) => (
<li key={session.id}>
<Link href="/sessions/[sessionId]" as={`/sessions/${session.id}`}>
<a>{session.name}</a>
</Link>
</li>
))}
</ul>
<button disabled={page === 0} onClick={goToPreviousPage}>
Previous
</button>
<button disabled={!hasMore} onClick={goToNextPage}>
Next
</button>
</div>
)
}
const SessionsPage: BlitzPage = () => {
return (
<div>
<Head>
<title>Sessions</title>
</Head>
<main>
<h1>Sessions</h1>
<p>
<Link href="/sessions/new">
<a>Create Session</a>
</Link>
</p>
<Suspense fallback={<div>Loading...</div>}>
<SessionsList />
</Suspense>
</main>
</div>
)
}
SessionsPage.getLayout = (page) => <Layout>{page}</Layout>
export default SessionsPage

View File

@@ -0,0 +1,21 @@
import {NotFoundError, SessionContext} from "blitz"
import db, {FindOneSessionArgs} from "db"
type GetSessionInput = {
where: FindOneSessionArgs["where"]
// Only available if a model relationship exists
// include?: FindOneSessionArgs['include']
}
export default async function getSession(
{where /* include */}: GetSessionInput,
ctx: {session?: SessionContext} = {},
) {
ctx.session!.authorize()
const session = await db.session.findOne({where})
if (!session) throw new NotFoundError()
return session
}

View File

@@ -0,0 +1,35 @@
import {SessionContext} from "blitz"
import db, {FindManySessionArgs} from "db"
type GetSessionsInput = {
where?: FindManySessionArgs["where"]
orderBy?: FindManySessionArgs["orderBy"]
skip?: FindManySessionArgs["skip"]
take?: FindManySessionArgs["take"]
// Only available if a model relationship exists
// include?: FindManySessionArgs['include']
}
export default async function getSessions(
{where, orderBy, skip = 0, take}: GetSessionsInput,
ctx: {session?: SessionContext} = {},
) {
ctx.session!.authorize()
const sessions = await db.session.findMany({
where,
orderBy,
take,
skip,
})
const count = await db.session.count()
const hasMore = typeof take === "number" ? skip + take < count : false
const nextPage = hasMore ? {take, skip: skip + take!} : null
return {
sessions,
nextPage,
hasMore,
}
}

View File

@@ -0,0 +1,23 @@
import React from "react"
type UserFormProps = {
initialValues: any
onSubmit: React.FormEventHandler<HTMLFormElement>
}
const UserForm = ({initialValues, onSubmit}: UserFormProps) => {
return (
<form
onSubmit={(event) => {
event.preventDefault()
onSubmit(event)
}}
>
<div>Put your form fields here. But for now, just click submit</div>
<div>{JSON.stringify(initialValues)}</div>
<button>Submit</button>
</form>
)
}
export default UserForm

View File

@@ -0,0 +1,10 @@
import db, {UserCreateArgs} from "db"
type CreateUserInput = {
data: UserCreateArgs["data"]
}
export default async function createUser({data}: CreateUserInput, ctx: Record<any, any> = {}) {
const user = await db.user.create({data})
return user
}

View File

@@ -0,0 +1,11 @@
import db, {UserDeleteArgs} from "db"
type DeleteUserInput = {
where: UserDeleteArgs["where"]
}
export default async function deleteUser({where}: DeleteUserInput, ctx: Record<any, any> = {}) {
const user = await db.user.delete({where})
return user
}

View File

@@ -1,9 +1,9 @@
import {Ctx} from "blitz"
import {SessionContext} from "blitz"
export default async function trackView(_ = null, {session}: Ctx) {
const currentViews = session.publicData.views || 0
await session.setPublicData({views: currentViews + 1})
await session.setPrivateData({views: currentViews + 1})
export default async function trackView(_ = null, ctx: {session?: SessionContext} = {}) {
const currentViews = ctx.session!.publicData.views || 0
await ctx.session!.setPublicData({views: currentViews + 1})
await ctx.session!.setPrivateData({views: currentViews + 1})
return
}

View File

@@ -0,0 +1,15 @@
import db, {UserUpdateArgs} from "db"
type UpdateUserInput = {
where: UserUpdateArgs["where"]
data: UserUpdateArgs["data"]
}
export default async function updateUser(
{where, data}: UpdateUserInput,
ctx: Record<any, any> = {},
) {
const user = await db.user.update({where, data})
return user
}

View File

@@ -0,0 +1,62 @@
import React, {Suspense} from "react"
import {Head, Link, useRouter, useQuery, useParam, BlitzPage} from "blitz"
import getUser from "app/users/queries/getUser"
import deleteUser from "app/users/mutations/deleteUser"
export const User = () => {
const router = useRouter()
const userId = useParam("userId", "number")
const [user] = useQuery(getUser, {where: {id: userId}})
return (
<div>
<h1>User {user.id}</h1>
<pre>{JSON.stringify(user, null, 2)}</pre>
{
<Link href="/users/[userId]/edit" as={`/users/${user.id}/edit`}>
<a>Edit</a>
</Link>
}
<button
type="button"
onClick={async () => {
if (window.confirm("This will be deleted")) {
await deleteUser({where: {id: user.id}})
router.push("/users")
}
}}
>
Delete
</button>
</div>
)
}
const ShowUserPage: BlitzPage = () => {
return (
<div>
<Head>
<title>User</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<p>
{
<Link href="/users">
<a>Users</a>
</Link>
}
</p>
<Suspense fallback={<div>Loading...</div>}>
<User />
</Suspense>
</main>
</div>
)
}
export default ShowUserPage

View File

@@ -0,0 +1,63 @@
import React, {Suspense} from "react"
import {Head, Link, useRouter, useQuery, useParam, BlitzPage} from "blitz"
import getUser from "app/users/queries/getUser"
import updateUser from "app/users/mutations/updateUser"
import UserForm from "app/users/components/UserForm"
export const EditUser = () => {
const router = useRouter()
const userId = useParam("userId", "number")
const [user, {mutate}] = useQuery(getUser, {where: {id: userId}})
return (
<div>
<h1>Edit User {user.id}</h1>
<pre>{JSON.stringify(user)}</pre>
<UserForm
initialValues={user}
onSubmit={async () => {
try {
const updated = await updateUser({
where: {id: user.id},
data: {name: "MyNewName"},
})
mutate(updated)
alert("Success!" + JSON.stringify(updated))
router.push("/users/[userId]", `/users/${updated.id}`)
} catch (error) {
console.log(error)
alert("Error creating user " + JSON.stringify(error, null, 2))
}
}}
/>
</div>
)
}
const EditUserPage: BlitzPage = () => {
return (
<div>
<Head>
<title>Edit User</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<Suspense fallback={<div>Loading...</div>}>
<EditUser />
</Suspense>
<p>
{
<Link href="/users">
<a>Users</a>
</Link>
}
</p>
</main>
</div>
)
}
export default EditUserPage

View File

@@ -1,60 +1,49 @@
import {Suspense} from "react"
import Layout from "app/layouts/Layout"
import {Link, usePaginatedQuery, useRouter, BlitzPage} from "blitz"
import React, {Suspense} from "react"
import {Head, Link, useQuery, BlitzPage} from "blitz"
import getUsers from "app/users/queries/getUsers"
const ITEMS_PER_PAGE = 100
import Layout from "app/layouts/Layout"
export const UsersList = () => {
const router = useRouter()
const page = Number(router.query.page) || 0
const [{users, hasMore}] = usePaginatedQuery(getUsers, {
orderBy: {id: "asc"},
skip: ITEMS_PER_PAGE * page,
take: ITEMS_PER_PAGE,
})
const goToPreviousPage = () => router.push({query: {page: page - 1}})
const goToNextPage = () => router.push({query: {page: page + 1}})
const [users] = useQuery(getUsers, {orderBy: {id: "desc"}})
return (
<div>
<ul>
{users.map((user) => (
<li key={user.id}>
<Link href="/users/[userId]" as={`/users/${user.id}`}>
<a>{user.email}</a>
</Link>
</li>
))}
</ul>
<button disabled={page === 0} onClick={goToPreviousPage}>
Previous
</button>
<button disabled={!hasMore} onClick={goToNextPage}>
Next
</button>
</div>
<ul>
{users?.map((user) => (
<li key={user.id}>
<Link href="/users/[userId]" as={`/users/${user.id}`}>
<a>{user.email}</a>
</Link>
</li>
))}
</ul>
)
}
const UsersPage: BlitzPage = () => {
return (
<div>
<p>
<Link href="/users/new">
<a>Create User</a>
</Link>
</p>
<Layout>
<Head>
<title>Users</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Suspense fallback={<div>Loading...</div>}>
<UsersList />
</Suspense>
</div>
<main>
<h1>Users</h1>
<p>
{
<Link href="/users/new">
<a>Create User</a>
</Link>
}
</p>
<Suspense fallback={<div>Loading...</div>}>
<UsersList />
</Suspense>
</main>
</Layout>
)
}
UsersPage.getLayout = (page) => <Layout>{page}</Layout>
export default UsersPage

View File

@@ -0,0 +1,44 @@
import React from "react"
import {Head, Link, useRouter, BlitzPage} from "blitz"
import createUser from "app/users/mutations/createUser"
import UserForm from "app/users/components/UserForm"
const NewUserPage: BlitzPage = () => {
const router = useRouter()
return (
<div>
<Head>
<title>New User</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<h1>Create New User </h1>
<UserForm
initialValues={{}}
onSubmit={async () => {
try {
const user = await createUser({data: {name: "MyName"}})
alert("Success!" + JSON.stringify(user))
router.push("/users/[userId]", `/users/${user.id}`)
} catch (error) {
alert("Error creating user " + JSON.stringify(error, null, 2))
}
}}
/>
<p>
{
<Link href="/users">
<a>Users</a>
</Link>
}
</p>
</main>
</div>
)
}
export default NewUserPage

View File

@@ -1,11 +1,11 @@
import {Ctx} from "blitz"
import db from "db"
import {SessionContext} from "blitz"
export default async function getCurrentUser(_ = null, ctx: Ctx) {
if (!ctx.session.userId) return null
export default async function getCurrentUser(_ = null, ctx: {session?: SessionContext} = {}) {
if (!ctx.session?.userId) return null
const user = await db().user.findFirst({
where: {id: ctx.session.userId},
const user = await db.user.findOne({
where: {id: ctx.session!.userId},
select: {id: true, name: true, email: true, role: true},
})

View File

@@ -1,18 +1,22 @@
import {Ctx, NotFoundError} from "blitz"
import db, {Prisma} from "db"
import db, {FindOneUserArgs} from "db"
import {SessionContext, NotFoundError} from "blitz"
type GetUserInput = {
where: Prisma.FindUniqueUserArgs["where"]
where: FindOneUserArgs["where"]
select?: FindOneUserArgs["select"]
// Only available if a model relationship exists
// include?: FindOneUserArgs['include']
}
export default async function getUser({where}: GetUserInput, ctx: Ctx) {
ctx.session.authorize()
export default async function getUser(
{where, select}: GetUserInput,
ctx: {session?: SessionContext} = {},
) {
ctx.session?.authorize(["admin", "user"])
const user = await db().user.findFirst({where})
const user = await db.user.findOne({where, select})
if (!user) throw new NotFoundError(`User with id ${where.id} does not exist`)
const {hashedPassword, ...rest} = user
return rest
return user
}

View File

@@ -1,26 +1,29 @@
import {Ctx} from "blitz"
import db, {FindManyUserArgs} from "db"
import {SessionContext} from "blitz"
type GetUsersInput = Pick<FindManyUserArgs, "where" | "orderBy" | "skip" | "take">
type GetUsersInput = {
where?: FindManyUserArgs["where"]
orderBy?: FindManyUserArgs["orderBy"]
cursor?: FindManyUserArgs["cursor"]
take?: FindManyUserArgs["take"]
skip?: FindManyUserArgs["skip"]
// Only available if a model relationship exists
// include?: FindManyUserArgs['include']
}
export default async function getUsers({where, orderBy, skip = 0, take}: GetUsersInput, ctx: Ctx) {
ctx.session.authorize()
export default async function getUsers(
{where, orderBy, cursor, take, skip}: GetUsersInput,
ctx: {session?: SessionContext} = {},
) {
ctx.session!.authorize(["admin"])
const users = await db().user.findMany({
const users = await db.user.findMany({
where,
orderBy,
cursor,
take,
skip,
})
const count = await db().user.count()
const hasMore = typeof take === "number" ? skip + take < count : false
const nextPage = hasMore ? {take, skip: skip + take!} : null
return {
users,
nextPage,
hasMore,
count,
}
return users
}

View File

@@ -1,4 +1,4 @@
const {sessionMiddleware, simpleRolesIsAuthorized} = require("@blitzjs/server")
const {sessionMiddleware, unstable_simpleRolesIsAuthorized} = require("@blitzjs/server")
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
})
@@ -6,16 +6,10 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({
module.exports = withBundleAnalyzer({
middleware: [
sessionMiddleware({
isAuthorized: simpleRolesIsAuthorized,
sessionExpiryMinutes: 4,
unstable_isAuthorized: unstable_simpleRolesIsAuthorized,
// sessionExpiryMinutes: 1,
}),
],
log: {
level: "trace",
},
experimental: {
isomorphicResolverImports: true,
},
/*
webpack: (config, {buildId, dev, isServer, defaultLoaders, webpack}) => {
// Note: we provide webpack above so you should not `require` it

View File

@@ -1,6 +1,5 @@
{
"baseUrl": "http://localhost:3099",
"defaultCommandTimeout": 10000,
"video": false,
"chromeWebSecurity": false
"video": false
}

View File

@@ -11,7 +11,7 @@ describe("index page", () => {
})
it("goes to the login page", () => {
cy.contains("a", /login/i).click()
cy.contains("a", "Log In").click()
cy.location("pathname").should("equal", "/login")
})
@@ -30,11 +30,11 @@ describe("index page", () => {
cy.signup(user)
cy.contains("button", "Logout").click()
cy.contains("a", /login/i).click()
cy.contains("a", "Log In").click()
cy.contains("Email").find("input").type(user.email)
cy.contains("Password").find("input").type(user.password)
cy.contains("button", /login/i).click()
cy.contains("button", "Log In").click()
cy.location("pathname").should("equal", "/")
cy.contains("button", "Logout")
@@ -48,7 +48,7 @@ describe("index page", () => {
cy.contains("button", "Logout").click()
cy.location("pathname").should("equal", "/")
cy.contains("a", /login/i)
cy.contains("a", "Log In")
})
it("tracks anonymous sessions", () => {

View File

@@ -15,7 +15,6 @@
/**
* @type {Cypress.PluginConfig}
*/
//@ts-ignore
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config

View File

@@ -1,7 +1,15 @@
import {PrismaClient} from "@prisma/client"
export * from "@prisma/client"
export default function db(): PrismaClient {
globalThis.prisma = globalThis.prisma || new PrismaClient()
return globalThis.prisma
let prisma: PrismaClient
if (process.env.NODE_ENV === "production") {
prisma = new PrismaClient()
} else {
// Ensure the prisma instance is re-used during hot-reloading
// Otherwise, a new client will be created on every reload
globalThis["prisma"] = globalThis["prisma"] || new PrismaClient()
prisma = globalThis["prisma"]
}
export default prisma

View File

@@ -1,30 +0,0 @@
const {pathsToModuleNameMapper} = require("ts-jest/utils")
const {compilerOptions} = require("./tsconfig")
module.exports = {
// Test setup file
setupFilesAfterEnv: ["<rootDir>/test/setup.ts"],
// Add type checking to Typescript test files
preset: "ts-jest",
testEnvironment: "jest-environment-jsdom-fourteen",
// Automatically clear mock calls and instances between every test
clearMocks: true,
testPathIgnorePatterns: ["/node_modules/", "/.blitz/", "/.next/", "<rootDir>/db/migrations"],
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(ts|tsx)$"],
transform: {
"^.+\\.(ts|tsx)$": "babel-jest",
},
// This makes absolute imports work
moduleDirectories: ["node_modules", "<rootDir>"],
modulePathIgnorePatterns: ["<rootDir>/.blitz", "<rootDir>/.next", "<rootDir>/cypress"],
moduleNameMapper: {
// This ensures any path aliases in tsconfig also work in jest
...pathsToModuleNameMapper(compilerOptions.paths || {}),
"\\.(css|less|sass|scss)$": "identity-obj-proxy",
"\\.(gif|ttf|eot|svg|png|jpg|jpeg)$": "<rootDir>/test/__mocks__/fileMock.js",
},
watchPlugins: ["jest-watch-typeahead/filename", "jest-watch-typeahead/testname"],
// Coverage output
coverageDirectory: ".coverage",
collectCoverageFrom: ["**/*.{js,jsx,ts,tsx}", "!**/*.d.ts", "!**/node_modules/**"],
}

View File

@@ -1,6 +1,6 @@
{
"name": "@examples/auth",
"version": "0.28.0-canary.1",
"version": "0.23.1-canary.0",
"scripts": {
"start": "blitz start",
"studio": "blitz db studio",
@@ -9,17 +9,12 @@
"analyze": "cross-env ANALYZE=true blitz build",
"cy:open": "cypress open",
"cy:run": "cypress run",
"test": "prisma generate && yarn test:jest && yarn test:e2e",
"test:jest": "jest",
"test:server": "blitz db migrate && blitz build && blitz start --production -p 3099",
"test:e2e": "cross-env NODE_ENV=test start-server-and-test test:server http://localhost:3099 cy:run"
"test:start": "blitz db migrate && blitz start --production -p 3099",
"test": "cross-env NODE_ENV=test start-server-and-test test:start http://localhost:3099 cy:run"
},
"browserslist": [
"defaults"
],
"prisma": {
"schema": "db/schema.prisma"
},
"prettier": {
"semi": false,
"printWidth": 100,
@@ -38,55 +33,47 @@
]
},
"dependencies": {
"@prisma/cli": "2.12.0",
"@prisma/client": "2.12.0",
"blitz": "0.28.0-canary.1",
"@prisma/cli": "2.4.1",
"@prisma/client": "2.4.1",
"blitz": "0.23.1-canary.0",
"final-form": "4.20.1",
"passport-auth0": "1.4.0",
"passport-github2": "0.1.12",
"passport-auth0": "1.3.3",
"passport-github2": "0.1.11",
"passport-twitter": "1.0.4",
"react": "0.0.0-experimental-3310209d0",
"react-dom": "0.0.0-experimental-3310209d0",
"react-error-boundary": "3.1.0",
"react-final-form": "6.5.2",
"react": "0.0.0-experimental-7f28234f8",
"react-dom": "0.0.0-experimental-7f28234f8",
"react-error-boundary": "2.3.1",
"react-final-form": "6.5.1",
"secure-password": "4.0.0",
"zod": "1.11.11"
"zod": "1.10.0"
},
"devDependencies": {
"@cypress/skip-test": "2.5.1",
"@cypress/skip-test": "2.5.0",
"@next/bundle-analyzer": "latest",
"@testing-library/jest-dom": "5.11.8",
"@testing-library/react": "11.2.2",
"@testing-library/react-hooks": "3.7.0",
"@types/jest": "26.0.19",
"@types/passport-auth0": "1.0.4",
"@types/passport-github2": "1.2.4",
"@types/passport-twitter": "1.0.36",
"@types/react": "17.0.0",
"@types/react": "16.9.38",
"@types/secure-password": "3.1.0",
"@typescript-eslint/eslint-plugin": "4.11.1",
"@typescript-eslint/parser": "4.11.1",
"@typescript-eslint/eslint-plugin": "2.34.1-alpha.2",
"@typescript-eslint/parser": "2.34.1-alpha.2",
"babel-eslint": "10.1.0",
"cross-env": "7.0.3",
"cypress": "6.2.0",
"eslint": "7.16.0",
"eslint-config-react-app": "6.0.0",
"eslint-plugin-cypress": "2.11.2",
"cross-env": "latest",
"cypress": "4.11.0",
"eslint": "7.6.0",
"eslint-config-react-app": "5.2.1",
"eslint-plugin-cypress": "2.11.1",
"eslint-plugin-flowtype": "5.2.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"husky": "4.3.6",
"jest": "26.6.3",
"jest-environment-jsdom-fourteen": "1.0.1",
"jest-watch-typeahead": "0.6.1",
"lint-staged": "10.5.3",
"prettier": "2.2.1",
"pretty-quick": "3.1.0",
"start-server-and-test": "1.11.7",
"ts-jest": "26.4.4",
"typescript": "4.1.3"
"eslint-plugin-import": "2.22.0",
"eslint-plugin-jsx-a11y": "6.3.1",
"eslint-plugin-react": "7.20.5",
"eslint-plugin-react-hooks": "4.0.8",
"husky": "4.2.5",
"lint-staged": "10.2.13",
"prettier": "2.0.5",
"pretty-quick": "2.0.1",
"start-server-and-test": "1.11.2",
"typescript": "3.9.5"
},
"private": true
}

View File

@@ -1 +0,0 @@
module.exports = "test-file-stub"

View File

@@ -1,6 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom/extend-expect"
require("dotenv-flow").config({silent: true})

View File

@@ -1,88 +0,0 @@
import {RouterContext, BlitzRouter} from "blitz"
import {render as defaultRender} from "@testing-library/react"
import {renderHook as defaultRenderHook} from "@testing-library/react-hooks"
export * from "@testing-library/react"
// --------------------------------------------------------------------------------
// This file customizes the render() and renderHook() test functions provided
// by React testing library. It adds a router context wrapper with a mocked router.
//
// You should always import `render` and `renderHook` from this file
//
// This is the place to add any other context providers you need while testing.
// --------------------------------------------------------------------------------
type DefaultParams = Parameters<typeof defaultRender>
type RenderUI = DefaultParams[0]
type RenderOptions = DefaultParams[1] & {router?: Partial<BlitzRouter>}
type DefaultHookParams = Parameters<typeof defaultRenderHook>
type RenderHook = DefaultHookParams[0]
type RenderHookOptions = DefaultHookParams[1] & {router?: Partial<BlitzRouter>}
// --------------------------------------------------
// render()
// --------------------------------------------------
// Override the default test render with our own
//
// You can override the router mock like this:
//
// const { baseElement } = render(<MyComponent />, {
// router: { pathname: '/my-custom-pathname' },
// });
// --------------------------------------------------
export function render(ui: RenderUI, {wrapper, router, ...options}: RenderOptions = {}) {
if (!wrapper) {
// Add a default context wrapper if one isn't supplied from the test
wrapper = ({children}) => (
<RouterContext.Provider value={{...mockRouter, ...router}}>{children}</RouterContext.Provider>
)
}
return defaultRender(ui, {wrapper, ...options})
}
// --------------------------------------------------
// renderHook()
// --------------------------------------------------
// Override the default test renderHook with our own
//
// You can override the router mock like this:
//
// const result = renderHook(() => myHook(), {
// router: { pathname: '/my-custom-pathname' },
// });
// --------------------------------------------------
export function renderHook(
hook: RenderHook,
{wrapper, router, ...options}: RenderHookOptions = {},
) {
if (!wrapper) {
// Add a default context wrapper if one isn't supplied from the test
wrapper = ({children}) => (
<RouterContext.Provider value={{...mockRouter, ...router}}>{children}</RouterContext.Provider>
)
}
return defaultRenderHook(hook, {wrapper, ...options})
}
export const mockRouter: BlitzRouter = {
basePath: "",
pathname: "/",
route: "/",
asPath: "/",
params: {},
query: {},
push: jest.fn(),
replace: jest.fn(),
reload: jest.fn(),
back: jest.fn(),
prefetch: jest.fn(),
beforePopState: jest.fn(),
events: {
on: jest.fn(),
off: jest.fn(),
emit: jest.fn(),
},
isFallback: false,
}

View File

@@ -1,12 +0,0 @@
import {DefaultCtx, SessionContext, DefaultPublicData} from "blitz"
import {User} from "db"
declare module "blitz" {
export interface Ctx extends DefaultCtx {
session: SessionContext
}
export interface PublicData extends DefaultPublicData {
userId: User["id"]
views?: number
}
}

View File

@@ -1,2 +0,0 @@
# This env file should be checked into source control
# This is the place for default values that should be used in all environments

View File

@@ -1,41 +0,0 @@
# Blitz Fauna Example
## Intro
This example shows how to use [Fauna](https://dashboard.fauna.com/accounts/register?utm_source=BlitzJS&utm_medium=sponsorship&utm_campaign=BlitzJS_Sponsorship_2020) instead of Prisma and Postgres.
The bulk of the integration is in the following files:
- `blitz.config.js`
- `db/index.ts`
And then also the queries and mutations use Fauna.
By far the main integration work is providing the auth session hooks for reading and writing session data to Fauna. All this is in `blitz.config.js`.
This example use the Fauna GraphQL API since it's more familiar to most people than FQL.
## Getting Started
1. Sign up for a Fauna account
1. Create a new database
1. Click on the GraphQL menu item
1. Upload the graphql schema located at `db/schema.graphql`
1. Click on the Security menu item
1. Create a new auth key, and add the auth key to `.env.local` like this:
```
FAUNA_SECRET=YOUR_AUTH_KEY
```
```
yarn blitz db migrate
```
2. Start the dev server
```
yarn blitz start
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

View File

@@ -1,67 +0,0 @@
import { AuthenticationError } from "blitz"
import SecurePassword from "secure-password"
import db from "db"
import { gql } from "graphql-request"
const SP = new SecurePassword()
export const hashPassword = async (password: string) => {
const hashedBuffer = await SP.hash(Buffer.from(password))
return hashedBuffer.toString("base64")
}
export const verifyPassword = async (hashedPassword: string, password: string) => {
try {
return await SP.verify(Buffer.from(password), Buffer.from(hashedPassword, "base64"))
} catch (error) {
console.error(error)
return false
}
}
export const authenticateUser = async (email: string, password: string) => {
const { user } = await db.request(
gql`
query getUser($email: String!) {
user: findUserByEmail(email: $email) {
id: _id
email
name
role
hashedPassword
}
}
`,
{ email: email.toLowerCase() }
)
if (!user || !user.hashedPassword) throw new AuthenticationError()
switch (await verifyPassword(user.hashedPassword, password)) {
case SecurePassword.VALID:
break
case SecurePassword.VALID_NEEDS_REHASH:
// Upgrade hashed password with a more secure hash
const improvedHash = await hashPassword(password)
await db.request(
gql`
mutation UpdateUser($data: UserInput!) {
updateUser(data: $data) {
id: _id
}
}
`,
{
data: {
id: user.id,
hashedPassword: improvedHash,
},
}
)
break
default:
throw new AuthenticationError()
}
const { hashedPassword, ...rest } = user
return rest
}

View File

@@ -1,49 +0,0 @@
import { Link, useMutation } from "blitz"
import { LabeledTextField } from "app/components/LabeledTextField"
import { Form, FORM_ERROR } from "app/components/Form"
import login from "app/auth/mutations/login"
import { LoginInput } from "app/auth/validations"
type LoginFormProps = {
onSuccess?: () => void
}
export const LoginForm = (props: LoginFormProps) => {
const [loginMutation] = useMutation(login)
return (
<div>
<h1>Login</h1>
<Form
submitText="Log In"
schema={LoginInput}
initialValues={{ email: "", password: "" }}
onSubmit={async (values) => {
try {
await loginMutation(values)
props.onSuccess?.()
} catch (error) {
if (error.name === "AuthenticationError") {
return { [FORM_ERROR]: "Sorry, those credentials are invalid" }
} else {
return {
[FORM_ERROR]:
"Sorry, we had an unexpected error. Please try again. - " + error.toString(),
}
}
}
}}
>
<LabeledTextField name="email" label="Email" placeholder="Email" />
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
</Form>
<div style={{ marginTop: "1rem" }}>
Or <Link href="/signup">Sign Up</Link>
</div>
</div>
)
}
export default LoginForm

View File

@@ -1,43 +0,0 @@
import { useMutation } from "blitz"
import { LabeledTextField } from "app/components/LabeledTextField"
import { Form, FORM_ERROR } from "app/components/Form"
import signup from "app/auth/mutations/signup"
import { SignupInput } from "app/auth/validations"
type SignupFormProps = {
onSuccess?: () => void
}
export const SignupForm = (props: SignupFormProps) => {
const [signupMutation] = useMutation(signup)
return (
<div>
<h1>Create an Account</h1>
<Form
submitText="Create Account"
schema={SignupInput}
initialValues={{ email: "", password: "" }}
onSubmit={async (values) => {
try {
await signupMutation(values)
props.onSuccess?.()
} catch (error) {
if (error.code === "P2002" && error.meta?.target?.includes("email")) {
// This error comes from Prisma
return { email: "This email is already being used" }
} else {
return { [FORM_ERROR]: error.toString() }
}
}
}}
>
<LabeledTextField name="email" label="Email" placeholder="Email" />
<LabeledTextField name="password" label="Password" placeholder="Password" type="password" />
</Form>
</div>
)
}
export default SignupForm

View File

@@ -1,15 +0,0 @@
import { Ctx } from "blitz"
import { authenticateUser } from "app/auth/auth-utils"
import { LoginInput, LoginInputType } from "../validations"
export default async function login(input: LoginInputType, { session }: Ctx) {
// This throws an error if input is invalid
const { email, password } = LoginInput.parse(input)
// This throws an error if credentials are invalid
const user = await authenticateUser(email, password)
await session.create({ userId: user.id, roles: [user.role] })
return user
}

View File

@@ -1,5 +0,0 @@
import { Ctx } from "blitz"
export default async function logout(_: any, { session }: Ctx) {
return await session.revoke()
}

View File

@@ -1,30 +0,0 @@
import { Ctx } from "blitz"
import db from "db"
import { hashPassword } from "app/auth/auth-utils"
import { SignupInput, SignupInputType } from "app/auth/validations"
import { gql } from "graphql-request"
export default async function signup(input: SignupInputType, { session }: Ctx) {
// This throws an error if input is invalid
const { email, password } = SignupInput.parse(input)
const hashedPassword = await hashPassword(password)
const { user } = await db.request(
gql`
mutation createUser($email: String!, $hashedPassword: String, $role: String!) {
user: createUser(data: { email: $email, hashedPassword: $hashedPassword, role: $role }) {
id: _id
email
name
role
}
}
`,
{ email: email.toLowerCase(), hashedPassword, role: "user" }
)
console.log("Create user result:", user)
await session.create({ userId: user.id, roles: [user.role] })
return user
}

View File

@@ -1,17 +0,0 @@
import { useRouter, BlitzPage } from "blitz"
import Layout from "app/layouts/Layout"
import { LoginForm } from "app/auth/components/LoginForm"
const LoginPage: BlitzPage = () => {
const router = useRouter()
return (
<div>
<LoginForm onSuccess={() => router.push("/")} />
</div>
)
}
LoginPage.getLayout = (page) => <Layout title="Log In">{page}</Layout>
export default LoginPage

View File

@@ -1,17 +0,0 @@
import { useRouter, BlitzPage } from "blitz"
import Layout from "app/layouts/Layout"
import { SignupForm } from "app/auth/components/SignupForm"
const SignupPage: BlitzPage = () => {
const router = useRouter()
return (
<div>
<SignupForm onSuccess={() => router.push("/")} />
</div>
)
}
SignupPage.getLayout = (page) => <Layout title="Sign Up">{page}</Layout>
export default SignupPage

View File

@@ -1,62 +0,0 @@
import { ReactNode, PropsWithoutRef } from "react"
import { Form as FinalForm, FormProps as FinalFormProps } from "react-final-form"
import * as z from "zod"
export { FORM_ERROR } from "final-form"
type FormProps<S extends z.ZodType<any, any>> = {
/** All your form fields */
children: ReactNode
/** Text to display in the submit button */
submitText: string
schema?: S
onSubmit: FinalFormProps<z.infer<S>>["onSubmit"]
initialValues?: FinalFormProps<z.infer<S>>["initialValues"]
} & Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit">
export function Form<S extends z.ZodType<any, any>>({
children,
submitText,
schema,
initialValues,
onSubmit,
...props
}: FormProps<S>) {
return (
<FinalForm
initialValues={initialValues}
validate={(values) => {
if (!schema) return
try {
schema.parse(values)
} catch (error) {
return error.formErrors.fieldErrors
}
}}
onSubmit={onSubmit}
render={({ handleSubmit, submitting, submitError }) => (
<form onSubmit={handleSubmit} className="form" {...props}>
{/* Form fields supplied as children are rendered here */}
{children}
{submitError && (
<div role="alert" style={{ color: "red" }}>
{submitError}
</div>
)}
<button type="submit" disabled={submitting}>
{submitText}
</button>
<style global jsx>{`
.form > * + * {
margin-top: 1rem;
}
`}</style>
</form>
)}
/>
)
}
export default Form

View File

@@ -1,57 +0,0 @@
import { PropsWithoutRef } from "react"
import { useField } from "react-final-form"
export interface LabeledTextFieldProps extends PropsWithoutRef<JSX.IntrinsicElements["input"]> {
/** Field name. */
name: string
/** Field label. */
label: string
/** Field type. Doesn't include radio buttons and checkboxes */
type?: "text" | "password" | "email" | "number"
outerProps?: PropsWithoutRef<JSX.IntrinsicElements["div"]>
}
export const LabeledTextField = React.forwardRef<HTMLInputElement, LabeledTextFieldProps>(
({ name, label, outerProps, ...props }, ref) => {
const {
input,
meta: { touched, error, submitError, submitting },
} = useField(name)
const normalizedError = Array.isArray(error) ? error.join(", ") : error || submitError
return (
<div {...outerProps}>
<label>
{label}
<input {...input} disabled={submitting} {...props} ref={ref} />
</label>
{touched && normalizedError && (
<div role="alert" style={{ color: "red" }}>
{normalizedError}
</div>
)}
<style jsx>{`
label {
display: flex;
flex-direction: column;
align-items: start;
font-size: 1rem;
}
input {
font-size: 1rem;
padding: 0.25rem 0.5rem;
border-radius: 3px;
border: 1px solid purple;
appearance: none;
margin-top: 0.5rem;
}
`}</style>
</div>
)
}
)
export default LabeledTextField

View File

@@ -1,7 +0,0 @@
import { useQuery } from "blitz"
import getCurrentUser from "app/users/queries/getCurrentUser"
export const useCurrentUser = () => {
const [user] = useQuery(getCurrentUser, null)
return user
}

View File

@@ -1,22 +0,0 @@
import { ReactNode } from "react"
import { Head } from "blitz"
type LayoutProps = {
title?: string
children: ReactNode
}
const Layout = ({ title, children }: LayoutProps) => {
return (
<>
<Head>
<title>{title || "fauna"}</title>
<link rel="icon" href="/favicon.ico" />
</Head>
{children}
</>
)
}
export default Layout

View File

@@ -1,19 +0,0 @@
import { Head, ErrorComponent } from "blitz"
// ------------------------------------------------------
// This page is rendered if a route match is not found
// ------------------------------------------------------
export default function Page404() {
const statusCode = 404
const title = "This page could not be found"
return (
<>
<Head>
<title>
{statusCode}: {title}
</title>
</Head>
<ErrorComponent statusCode={statusCode} title={title} />
</>
)
}

View File

@@ -1,43 +0,0 @@
import { AppProps, ErrorComponent, useRouter } from "blitz"
import { ErrorBoundary, FallbackProps } from "react-error-boundary"
import { queryCache } from "react-query"
import LoginForm from "app/auth/components/LoginForm"
export default function App({ Component, pageProps }: AppProps) {
const getLayout = Component.getLayout || ((page) => page)
const router = useRouter()
return (
<ErrorBoundary
FallbackComponent={RootErrorFallback}
resetKeys={[router.asPath]}
onReset={() => {
// This ensures the Blitz useQuery hooks will automatically refetch
// data any time you reset the error boundary
queryCache.resetErrorBoundaries()
}}
>
{getLayout(<Component {...pageProps} />)}
</ErrorBoundary>
)
}
function RootErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
if (error?.name === "AuthenticationError") {
return <LoginForm onSuccess={resetErrorBoundary} />
} else if (error?.name === "AuthorizationError") {
return (
<ErrorComponent
statusCode={(error as any).statusCode}
title="Sorry, you are not authorized to access this"
/>
)
} else {
return (
<ErrorComponent
statusCode={(error as any)?.statusCode || 400}
title={error?.message || error?.name}
/>
)
}
}

View File

@@ -1,25 +0,0 @@
import { render } from "test/utils"
import Home from "./index"
import { useCurrentUser } from "app/hooks/useCurrentUser"
jest.mock("app/hooks/useCurrentUser")
const mockUseCurrentUser = useCurrentUser as jest.MockedFunction<typeof useCurrentUser>
test.skip("renders blitz documentation link", () => {
// This is an example of how to ensure a specific item is in the document
// But it's disabled by default (by test.skip) so the test doesn't fail
// when you remove the the default content from the page
// This is an example on how to mock api hooks when testing
mockUseCurrentUser.mockReturnValue({
id: 1,
name: "User",
email: "user@email.com",
role: "user",
})
const { getByText } = render(<Home />)
const linkElement = getByText(/Documentation/i)
expect(linkElement).toBeInTheDocument()
})

View File

@@ -1,272 +0,0 @@
import { Link, BlitzPage, useMutation } from "blitz"
import Layout from "app/layouts/Layout"
import logout from "app/auth/mutations/logout"
import { useCurrentUser } from "app/hooks/useCurrentUser"
import { Suspense } from "react"
/*
* This file is just for a pleasant getting started page for your new app.
* You can delete everything in here and start from scratch if you like.
*/
const UserInfo = () => {
const currentUser = useCurrentUser()
const [logoutMutation] = useMutation(logout)
if (currentUser) {
return (
<>
<button
className="button small"
onClick={async () => {
await logoutMutation()
}}
>
Logout
</button>
<div>
User id: <code>{currentUser.id}</code>
<br />
User role: <code>{currentUser.role}</code>
</div>
</>
)
} else {
return (
<>
<Link href="/signup">
<a className="button small">
<strong>Sign Up</strong>
</a>
</Link>
<Link href="/login">
<a className="button small">
<strong>Login</strong>
</a>
</Link>
</>
)
}
}
const Home: BlitzPage = () => {
return (
<div className="container">
<main>
<div className="logo">
<img src="/logo.png" alt="blitz.js" />
</div>
<p>
<strong>Congrats!</strong> Your app is ready, including user sign-up and log-in.
</p>
<div className="buttons" style={{ marginTop: "1rem", marginBottom: "1rem" }}>
<Suspense fallback="Loading...">
<UserInfo />
</Suspense>
</div>
<p>
<strong>
To add a new model to your app, <br />
run the following in your terminal:
</strong>
</p>
<pre>
<code>blitz generate all project name:string</code>
</pre>
<pre>
<code>blitz db migrate</code>
</pre>
<div>
<p>
Then <strong>restart the server</strong>
</p>
<pre>
<code>Ctrl + c</code>
</pre>
<pre>
<code>blitz start</code>
</pre>
<p>
and go to{" "}
<Link href="/projects">
<a>/projects</a>
</Link>
</p>
</div>
<div className="buttons" style={{ marginTop: "5rem" }}>
<a
className="button"
href="https://blitzjs.com/docs/getting-started?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
<a
className="button-outline"
href="https://github.com/blitz-js/blitz"
target="_blank"
rel="noopener noreferrer"
>
Github Repo
</a>
<a
className="button-outline"
href="https://slack.blitzjs.com"
target="_blank"
rel="noopener noreferrer"
>
Slack Community
</a>
</div>
</main>
<footer>
<a
href="https://blitzjs.com?utm_source=blitz-new&utm_medium=app-template&utm_campaign=blitz-new"
target="_blank"
rel="noopener noreferrer"
>
Powered by Blitz.js
</a>
</footer>
<style jsx global>{`
@import url("https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@300;700&display=swap");
html,
body {
padding: 0;
margin: 0;
font-family: "Libre Franklin", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
box-sizing: border-box;
}
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
main {
padding: 5rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
main p {
font-size: 1.2rem;
}
p {
text-align: center;
}
footer {
width: 100%;
height: 60px;
border-top: 1px solid #eaeaea;
display: flex;
justify-content: center;
align-items: center;
background-color: #45009d;
}
footer a {
display: flex;
justify-content: center;
align-items: center;
}
footer a {
color: #f4f4f4;
text-decoration: none;
}
.logo {
margin-bottom: 2rem;
}
.logo img {
width: 300px;
}
.buttons {
display: grid;
grid-auto-flow: column;
grid-gap: 0.5rem;
}
.button {
font-size: 1rem;
background-color: #6700eb;
padding: 1rem 2rem;
color: #f4f4f4;
text-align: center;
}
.button.small {
padding: 0.5rem 1rem;
}
.button:hover {
background-color: #45009d;
}
.button-outline {
border: 2px solid #6700eb;
padding: 1rem 2rem;
color: #6700eb;
text-align: center;
}
.button-outline:hover {
border-color: #45009d;
color: #45009d;
}
pre {
background: #fafafa;
border-radius: 5px;
padding: 0.75rem;
text-align: center;
}
code {
font-size: 0.9rem;
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}
.grid {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
max-width: 800px;
margin-top: 3rem;
}
@media (max-width: 600px) {
.grid {
width: 100%;
flex-direction: column;
}
}
`}</style>
</div>
)
}
Home.getLayout = (page) => <Layout title="Home">{page}</Layout>
export default Home

View File

@@ -1,23 +0,0 @@
import { Ctx } from "blitz"
import db from "db"
import { gql } from "graphql-request"
export default async function getCurrentUser(_ = null, { session }: Ctx) {
if (!session.userId) return null
const { user } = await db.request(
gql`
query getUser($id: ID!) {
user: findUserByID(id: $id) {
id: _id
email
name
role
}
}
`,
{ id: session.userId }
)
return user
}

View File

@@ -1,161 +0,0 @@
const { sessionMiddleware, simpleRolesIsAuthorized } = require("@blitzjs/server")
const { GraphQLClient, gql } = require("graphql-request")
const graphQLClient = new GraphQLClient("https://graphql.fauna.com/graphql", {
headers: {
authorization: "Bearer " + process.env.FAUNA_SECRET,
},
})
const normalizeSession = (faunaSession) => {
if (!faunaSession) return null
const { user, expiresAt, ...rest } = faunaSession
return {
...rest,
userId: user.id,
expiresAt: new Date(expiresAt),
}
}
module.exports = {
middleware: [
sessionMiddleware({
isAuthorized: simpleRolesIsAuthorized,
getSession: async (handle) => {
const { findSessionByHandle: session } = await graphQLClient.request(
gql`
query getSession($handle: String!) {
findSessionByHandle(handle: $handle) {
id: _id
publicData
privateData
antiCSRFToken
expiresAt
hashedSessionToken
handle
user {
id: _id
}
}
}
`,
{ handle: handle }
)
if (!session) return null
const { user, expiresAt, ...rest } = session
return {
...rest,
userId: user.id,
expiresAt: new Date(expiresAt),
}
},
// getSessions: (userId) => getDb().session.findMany({ where: { userId } }),
createSession: async (session) => {
const { userId, ...sessionInput } = session
const userInput = { connect: userId }
const { createSession: sessionRes } = await graphQLClient.request(
gql`
mutation CreateSession($data: SessionInput!) {
createSession(data: $data) {
id: _id
publicData
privateData
antiCSRFToken
expiresAt
hashedSessionToken
handle
user {
id: _id
}
}
}
`,
{
data: {
...sessionInput,
expiresAt: sessionInput.expiresAt.toISOString(),
user: userInput,
},
}
)
return normalizeSession(sessionRes)
},
updateSession: async (sessionHandle, session) => {
const { findSessionByHandle: existingSession } = await graphQLClient.request(
gql`
query getSession($handle: String!) {
findSessionByHandle(handle: $handle) {
id: _id
}
}
`,
{ handle: sessionHandle }
)
const { userId, handle, ...sessionInput } = session
const { updateSession: sessionRes } = await graphQLClient.request(
gql`
mutation UpdateSession($data: SessionInput!) {
updateSession(data: $data) {
id: _id
publicData
privateData
antiCSRFToken
expiresAt
hashedSessionToken
handle
user {
id: _id
}
}
}
`,
{
data: {
...sessionInput,
id: existingSession.id,
expiresAt: sessionInput.expiresAt.toISOString(),
},
}
)
return normalizeSession(sessionRes)
},
deleteSession: async (handle) => {
const { findSessionByHandle: existingSession } = await graphQLClient.request(
gql`
query getSession($handle: String!) {
findSessionByHandle(handle: $handle) {
id: _id
}
}
`,
{ handle: handle }
)
await graphQLClient.request(
gql`
mutation DeleteSession($id ID!) {
deleteSession(id: $id) {
id: _id
handle
}
}
`,
{
id: existingSession.id,
}
)
},
}),
],
/* Uncomment this to customize the webpack config
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
// Note: we provide webpack above so you should not `require` it
// Perform customizations to webpack config
// Important: return the modified config
return config
},
*/
}

View File

@@ -1,9 +0,0 @@
import { GraphQLClient } from "graphql-request"
const graphQLClient = new GraphQLClient("https://graphql.fauna.com/graphql", {
headers: {
authorization: "Bearer " + process.env.FAUNA_SECRET,
},
})
export default graphQLClient

View File

@@ -1,23 +0,0 @@
type User {
name: String
email: String! @unique
hashedPassword: String
role: String!
sessions: [Session!] @relation
}
type Session {
expiresAt: Time
handle: String! @unique
user: User
hashedSessionToken: String
antiCSRFToken: String
publicData: String
privateData: String
}
type Query {
allUsers: [User!]
findUserByEmail(email: String!): User @index(name: "unique_User_email")
findSessionByHandle(handle: String!): Session @index(name: "unique_Session_handle")
}

View File

@@ -1,16 +0,0 @@
// import db from "./index"
/*
* This seed function is executed when you run `blitz db seed`.
*
* Probably you want to use a library like https://chancejs.com
* or https://github.com/Marak/Faker.js to easily generate
* realistic data.
*/
const seed = async () => {
// for (let i = 0; i < 5; i++) {
// await db.project.create({ data: { name: "Project " + i } })
// }
}
export default seed

View File

@@ -1,30 +0,0 @@
const { pathsToModuleNameMapper } = require("ts-jest/utils")
const { compilerOptions } = require("./tsconfig")
module.exports = {
// Test setup file
setupFilesAfterEnv: ["<rootDir>/test/setup.ts"],
// Add type checking to Typescript test files
preset: "ts-jest",
testEnvironment: "jest-environment-jsdom-fourteen",
// Automatically clear mock calls and instances between every test
clearMocks: true,
testPathIgnorePatterns: ["/node_modules/", "/.blitz/", "/.next/", "<rootDir>/db/migrations"],
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(ts|tsx)$"],
transform: {
"^.+\\.(ts|tsx)$": "babel-jest",
},
// This makes absolute imports work
moduleDirectories: ["node_modules", "."],
modulePathIgnorePatterns: [".blitz"],
moduleNameMapper: {
// This ensures any path aliases in tsconfig also work in jest
...pathsToModuleNameMapper(compilerOptions.paths || {}),
"\\.(css|less|sass|scss)$": "identity-obj-proxy",
"\\.(gif|ttf|eot|svg|png|jpg|jpeg)$": "<rootDir>/test/__mocks__/fileMock.js",
},
watchPlugins: ["jest-watch-typeahead/filename", "jest-watch-typeahead/testname"],
// Coverage output
coverageDirectory: ".coverage",
collectCoverageFrom: ["**/*.{js,jsx,ts,tsx}", "!**/*.d.ts", "!**/node_modules/**"],
}

View File

@@ -1,70 +0,0 @@
{
"name": "@examples/fauna",
"version": "0.28.0-canary.1",
"scripts": {
"start": "blitz start",
"studio": "blitz db studio",
"build": "blitz build",
"lint": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx .",
"test": "echo \"No tests yet\""
},
"browserslist": [
"defaults"
],
"prettier": {
"semi": false,
"printWidth": 100
},
"husky": {
"hooks": {
"pre-commit": "tsc && lint-staged && pretty-quick --staged",
"pre-push": "npm run lint && npm run test"
}
},
"lint-staged": {
"*.{js,ts,tsx}": [
"eslint --fix"
]
},
"dependencies": {
"blitz": "0.28.0-canary.1",
"final-form": "4.20.1",
"graphql": "15.4.0",
"graphql-request": "3.4.0",
"react": "0.0.0-experimental-3310209d0",
"react-dom": "0.0.0-experimental-3310209d0",
"react-error-boundary": "3.1.0",
"react-final-form": "6.5.2",
"secure-password": "4.0.0",
"zod": "1.11.11"
},
"devDependencies": {
"@testing-library/jest-dom": "5.11.8",
"@testing-library/react": "11.2.2",
"@testing-library/react-hooks": "3.7.0",
"@types/jest": "26.0.19",
"@types/react": "17.0.0",
"@types/secure-password": "3.1.0",
"@typescript-eslint/eslint-plugin": "4.11.1",
"@typescript-eslint/parser": "4.11.1",
"babel-eslint": "10.1.0",
"eslint": "7.16.0",
"eslint-config-react-app": "6.0.0",
"eslint-plugin-flowtype": "5.2.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"husky": "4.3.6",
"jest": "26.6.3",
"jest-environment-jsdom-fourteen": "1.0.1",
"jest-watch-typeahead": "0.6.1",
"lint-staged": "10.5.3",
"prettier": "2.2.1",
"pretty-quick": "3.1.0",
"start-server-and-test": "1.11.7",
"ts-jest": "26.4.4",
"typescript": "4.1.3"
},
"private": true
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -1 +0,0 @@
module.exports = "test-file-stub"

View File

@@ -1,6 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom/extend-expect"
require("dotenv-flow").config({ silent: true })

View File

@@ -1,92 +0,0 @@
import { RouterContext, BlitzRouter } from "blitz"
import { render as defaultRender } from "@testing-library/react"
import { renderHook as defaultRenderHook } from "@testing-library/react-hooks"
export * from "@testing-library/react"
// --------------------------------------------------------------------------------
// This file customizes the render() and renderHook() test functions provided
// by React testing library. It adds a router context wrapper with a mocked router.
//
// You should always import `render` and `renderHook` from this file
//
// This is the place to add any other context providers you need while testing.
// --------------------------------------------------------------------------------
type DefaultParams = Parameters<typeof defaultRender>
type RenderUI = DefaultParams[0]
type RenderOptions = DefaultParams[1] & { router?: Partial<BlitzRouter> }
type DefaultHookParams = Parameters<typeof defaultRenderHook>
type RenderHook = DefaultHookParams[0]
type RenderHookOptions = DefaultHookParams[1] & { router?: Partial<BlitzRouter> }
// --------------------------------------------------
// render()
// --------------------------------------------------
// Override the default test render with our own
//
// You can override the router mock like this:
//
// const { baseElement } = render(<MyComponent />, {
// router: { pathname: '/my-custom-pathname' },
// });
// --------------------------------------------------
export function render(ui: RenderUI, { wrapper, router, ...options }: RenderOptions = {}) {
if (!wrapper) {
// Add a default context wrapper if one isn't supplied from the test
wrapper = ({ children }) => (
<RouterContext.Provider value={{ ...mockRouter, ...router }}>
{children}
</RouterContext.Provider>
)
}
return defaultRender(ui, { wrapper, ...options })
}
// --------------------------------------------------
// renderHook()
// --------------------------------------------------
// Override the default test renderHook with our own
//
// You can override the router mock like this:
//
// const result = renderHook(() => myHook(), {
// router: { pathname: '/my-custom-pathname' },
// });
// --------------------------------------------------
export function renderHook(
hook: RenderHook,
{ wrapper, router, ...options }: RenderHookOptions = {}
) {
if (!wrapper) {
// Add a default context wrapper if one isn't supplied from the test
wrapper = ({ children }) => (
<RouterContext.Provider value={{ ...mockRouter, ...router }}>
{children}
</RouterContext.Provider>
)
}
return defaultRenderHook(hook, { wrapper, ...options })
}
export const mockRouter: BlitzRouter = {
basePath: "",
pathname: "/",
route: "/",
asPath: "/",
params: {},
query: {},
push: jest.fn(),
replace: jest.fn(),
reload: jest.fn(),
back: jest.fn(),
prefetch: jest.fn(),
beforePopState: jest.fn(),
events: {
on: jest.fn(),
off: jest.fn(),
emit: jest.fn(),
},
isFallback: false,
}

View File

@@ -1,11 +0,0 @@
import { DefaultCtx, SessionContext, DefaultPublicData } from "blitz"
import { User } from "db"
declare module "blitz" {
export interface Ctx extends DefaultCtx {
session: SessionContext
}
export interface PublicData extends DefaultPublicData {
userId: User["id"]
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "no-prisma",
"version": "0.28.0-canary.1",
"version": "0.23.1-canary.0",
"scripts": {
"start": "blitz start",
"build": "blitz build",
@@ -26,29 +26,29 @@
]
},
"dependencies": {
"blitz": "0.28.0-canary.1",
"knex": "0.21.15",
"react": "0.0.0-experimental-3310209d0",
"react-dom": "0.0.0-experimental-3310209d0",
"blitz": "0.23.1-canary.0",
"knex": "0.21.2",
"react": "0.0.0-experimental-7f28234f8",
"react-dom": "0.0.0-experimental-7f28234f8",
"sqlite3": "5.0.0"
},
"devDependencies": {
"@types/react": "17.0.0",
"@typescript-eslint/eslint-plugin": "4.11.1",
"@typescript-eslint/parser": "4.11.1",
"@types/react": "16.9.43",
"@typescript-eslint/eslint-plugin": "2.34.1-alpha.2",
"@typescript-eslint/parser": "2.34.1-alpha.2",
"babel-eslint": "10.1.0",
"eslint": "7.16.0",
"eslint-config-react-app": "6.0.0",
"eslint": "7.6.0",
"eslint-config-react-app": "5.2.1",
"eslint-plugin-flowtype": "5.2.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"husky": "4.3.6",
"lint-staged": "10.5.3",
"prettier": "2.2.1",
"pretty-quick": "3.1.0",
"typescript": "4.1.3"
"eslint-plugin-import": "2.22.0",
"eslint-plugin-jsx-a11y": "6.3.1",
"eslint-plugin-react": "7.20.5",
"eslint-plugin-react-hooks": "4.0.8",
"husky": "4.2.5",
"lint-staged": "10.2.13",
"prettier": "2.0.5",
"pretty-quick": "2.0.1",
"typescript": "3.9.6"
},
"private": true
}

View File

@@ -29,7 +29,7 @@ export const EditProject = () => {
},
})
alert("Success!" + JSON.stringify(updated))
router.push(`/projects/${updated.id}`)
router.push("/projects/[id]", `/projects/${updated.id}`)
} catch (error) {
alert("Error creating project " + JSON.stringify(error, null, 2))
}

View File

@@ -24,7 +24,7 @@ const NewProjectPage = () => {
},
})
alert("Success!" + JSON.stringify(project))
router.push(`/projects/${project.id}`)
router.push("/projects/[id]", `/projects/${project.id}`)
} catch (error) {
alert("Error creating project " + JSON.stringify(error, null, 2))
}

View File

@@ -1,5 +1,5 @@
import db from "db"
export default async function getProject(args) {
const project = await db.project.findFirst(args)
const project = await db.project.findOne(args)
return project
}

View File

@@ -1,6 +1,6 @@
{
"name": "@examples/plain-js",
"version": "0.28.0-canary.1",
"version": "0.23.1-canary.0",
"scripts": {
"start": "blitz start",
"build": "blitz db migrate && blitz build",
@@ -29,28 +29,29 @@
]
},
"dependencies": {
"@prisma/cli": "2.12.0",
"@prisma/client": "2.12.0",
"blitz": "0.28.0-canary.1",
"react": "0.0.0-experimental-3310209d0",
"react-dom": "0.0.0-experimental-3310209d0"
"@prisma/cli": "2.4.1",
"@prisma/client": "2.4.1",
"blitz": "0.23.1-canary.0",
"react": "0.0.0-experimental-7f28234f8",
"react-dom": "0.0.0-experimental-7f28234f8"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "4.11.1",
"@typescript-eslint/parser": "4.11.1",
"@types/react": "16.9.35",
"@typescript-eslint/eslint-plugin": "2.31.0",
"@typescript-eslint/parser": "2.31.0",
"babel-eslint": "10.1.0",
"eslint": "7.16.0",
"eslint-config-react-app": "6.0.0",
"eslint": "7.6.0",
"eslint-config-react-app": "5.2.1",
"eslint-plugin-flowtype": "5.2.0",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"husky": "4.3.6",
"lint-staged": "10.5.3",
"prettier": "2.2.1",
"pretty-quick": "3.1.0",
"typescript": "4.1.3"
"eslint-plugin-import": "2.22.0",
"eslint-plugin-jsx-a11y": "6.3.1",
"eslint-plugin-react": "7.20.5",
"eslint-plugin-react-hooks": "4.0.8",
"husky": "4.2.5",
"lint-staged": "10.2.2",
"prettier": "2.0.5",
"pretty-quick": "2.0.1",
"typescript": "3.9.2"
},
"private": true
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +0,0 @@
import db, {Product} from "db"
import {BlitzApiRequest, BlitzApiResponse} from "blitz"
import {mean} from "lodash"
// this is here mainly as an integration test for
// importing from api/
export function meanPrice(products: Product[]) {
const prices = products.map((p) => p.price).filter((p) => !!p) as number[]
return mean(prices)
}
export default async function users(_req: BlitzApiRequest, res: BlitzApiResponse) {
const products = await db.product.findMany()
res.status(200).send(products)
}

View File

@@ -1,4 +0,0 @@
/* integration test for scss modules */
.red {
border: 2px solid red;
}

View File

@@ -1,11 +1,9 @@
import {Link} from "blitz"
import styles from "./index.module.scss"
function StoreAdminPage() {
return (
<div>
<h1 className={styles.red}>Store Admin</h1>
<h1>Store Admin</h1>
<div>
<p>
<Link href="/admin/products">

View File

@@ -2,20 +2,19 @@ import {Suspense} from "react"
import {Link, useRouter, useQuery, useParam} from "blitz"
import getProduct from "app/products/queries/getProduct"
import ProductForm from "app/products/components/ProductForm"
import {queryCache} from "react-query"
function Product() {
const router = useRouter()
const id = useParam("id", "number")
const [product] = useQuery(getProduct, {where: {id}})
// Here to test for https://github.com/blitz-js/blitz/issues/1443
if (!product) throw new Error("useQuery did not throw!")
return (
<ProductForm
product={product}
onSuccess={async () => {
await router.push("/admin/products")
onSuccess={() => {
queryCache.invalidateQueries("/api/products/queries/getProducts")
router.push("/admin/products")
}}
/>
)

View File

@@ -1,56 +1,28 @@
import {Suspense, useState} from "react"
import {useQuery, Link, useRouterQuery, invalidateQuery, setQueryData} from "blitz"
import {Suspense} from "react"
import {useQuery, Link, useRouterQuery} from "blitz"
import getProducts from "app/products/queries/getProducts"
import {meanPrice} from "app/admin/api/users"
function reversedProductList(productsList) {
return {...productsList, products: [...productsList.products].reverse()}
}
import getProduct from "app/products/queries/getProduct"
function ProductsList() {
const {orderby = "id", order = "desc"} = useRouterQuery()
const [refetch, setRefetch] = useState(false)
const params = {
const [{products}] = useQuery(getProducts, {
orderBy: {
[Array.isArray(orderby) ? orderby[0] : orderby]: order,
},
}
const [{products}] = useQuery(getProducts, params)
})
return (
<>
<button onClick={() => setQueryData(getProducts, params, reversedProductList, {refetch})}>
Reverse
</button>
<label>
<input
name="refetch"
type="checkbox"
checked={refetch}
onChange={(event) => setRefetch(event.target.checked)}
/>
Refetch
</label>
<ul>
{products.map((product) => (
<li key={product.id}>
<Link href="/admin/products/[id]" as={`/admin/products/${product.id}`}>
<a
// Disable until prefetch api added
//onMouseEnter={() => getProduct({where: {id: product.id}})}
>
{product.name}
</a>
</Link>{" "}
- Created: {product.createdAt.toISOString()}
</li>
))}
</ul>
<p>Mean price: {meanPrice(products)}</p>
</>
<ul>
{products.map((product) => (
<li key={product.id}>
<Link href="/admin/products/[id]" as={`/admin/products/${product.id}`}>
<a onMouseEnter={() => getProduct({where: {id: product.id}})}>{product.name}</a>
</Link>{" "}
- Created: {product.createdAt.toISOString()}
</li>
))}
</ul>
)
}
@@ -59,8 +31,6 @@ function AdminProducts() {
<div>
<h1>Products</h1>
<button onClick={() => invalidateQuery(getProducts)}>Invalidate query</button>
<p>
<Link href="/admin/products/new">
<a>Create Product</a>

Some files were not shown because too many files have changed in this diff Show More