Compare commits
38 Commits
subtemplat
...
@blitzjs/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb32903bf9 | ||
|
|
8dedca1a29 | ||
|
|
b1ef45bf28 | ||
|
|
c6e8df5efd | ||
|
|
46a34c7b3a | ||
|
|
18d4ef74a9 | ||
|
|
212a1cb941 | ||
|
|
151dcc308e | ||
|
|
fd90cbedb4 | ||
|
|
10d27c74af | ||
|
|
9810d984f1 | ||
|
|
10574b7359 | ||
|
|
c28684b8e5 | ||
|
|
a590820c14 | ||
|
|
ff3ef58a47 | ||
|
|
e33c81a70f | ||
|
|
dc41f8eedc | ||
|
|
f49aa37892 | ||
|
|
e218d5f84d | ||
|
|
2eb4f791a8 | ||
|
|
fa0850d0a3 | ||
|
|
2d3c6cbfb8 | ||
|
|
6ff7e99d15 | ||
|
|
77ca03af96 | ||
|
|
f56ef9b2c5 | ||
|
|
991b4a9db0 | ||
|
|
0c8edbb8b2 | ||
|
|
2d75a2b7c7 | ||
|
|
b878c6845e | ||
|
|
cb0da6a0cb | ||
|
|
a47c643145 | ||
|
|
945c66a53c | ||
|
|
50421a19ed | ||
|
|
926e5cf10f | ||
|
|
6228366ea3 | ||
|
|
bcb56eb79d | ||
|
|
80f0c81130 | ||
|
|
0847896968 |
@@ -3590,69 +3590,6 @@
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dineshgadge",
|
||||
"name": "Dinesh Gadge",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/186976?v=4",
|
||||
"profile": "https://github.com/dineshgadge",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "maltekiessling",
|
||||
"name": "Malte Kießling",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/30420110?v=4",
|
||||
"profile": "https://github.com/maltekiessling",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ospfranco",
|
||||
"name": "Oscar Franco",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1634213?v=4",
|
||||
"profile": "ospfranco.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Nfinished",
|
||||
"name": "Adam Trager",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1719791?v=4",
|
||||
"profile": "adamtrager.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "shellord",
|
||||
"name": "saheenshoukath",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2632896?v=4",
|
||||
"profile": "https://saheen.codes",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "husnuljahneer",
|
||||
"name": "Husnul Jahneer",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/54552763?v=4",
|
||||
"profile": "https://jahneer.me",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ReykCS",
|
||||
"name": "Reyk",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/40463716?v=4",
|
||||
"profile": "https://github.com/ReykCS",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
8
.changeset/README.md
Normal file
8
.changeset/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||
11
.changeset/config.json
Normal file
11
.changeset/config.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [["blitz"], ["@blitzjs/*"]],
|
||||
"linked": [],
|
||||
"access": "restricted",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": ["web", "test-*"]
|
||||
}
|
||||
5
.changeset/nine-onions-admire.md
Normal file
5
.changeset/nine-onions-admire.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"blitz": patch
|
||||
---
|
||||
|
||||
downgrade pkg-dir to non-esm only version
|
||||
9
.changeset/ninety-pets-heal.md
Normal file
9
.changeset/ninety-pets-heal.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
"blitz": patch
|
||||
"@blitzjs/auth": patch
|
||||
"@blitzjs/next": patch
|
||||
"@blitzjs/rpc": patch
|
||||
"@blitzjs/generator": patch
|
||||
---
|
||||
|
||||
initial publish
|
||||
5
.changeset/poor-peas-lick.md
Normal file
5
.changeset/poor-peas-lick.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@blitzjs/generator": patch
|
||||
---
|
||||
|
||||
fix generator npm package dist
|
||||
18
.changeset/pre.json
Normal file
18
.changeset/pre.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"mode": "pre",
|
||||
"tag": "alpha",
|
||||
"initialVersions": {
|
||||
"web": "0.0.0",
|
||||
"test-auth": "0.0.0",
|
||||
"test-rpc": "0.0.0",
|
||||
"test-utils": "0.0.0",
|
||||
"blitz": "2.0.0-alpha.0",
|
||||
"@blitzjs/auth": "2.0.0-alpha.0",
|
||||
"@blitzjs/next": "2.0.0-alpha.0",
|
||||
"@blitzjs/rpc": "2.0.0-alpha.0",
|
||||
"@blitzjs/config": "0.0.0",
|
||||
"@blitzjs/generator": "2.0.0-alpha.0",
|
||||
"template": "0.0.0"
|
||||
},
|
||||
"changesets": ["nine-onions-admire", "ninety-pets-heal", "poor-peas-lick", "ten-rivers-burn"]
|
||||
}
|
||||
5
.changeset/ten-rivers-burn.md
Normal file
5
.changeset/ten-rivers-burn.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"blitz": patch
|
||||
---
|
||||
|
||||
fix more cli problems
|
||||
@@ -1,55 +1,2 @@
|
||||
node_modules
|
||||
reports
|
||||
*.log
|
||||
.nyc_output
|
||||
**/coverage
|
||||
tsconfig.tsbuildinfo
|
||||
**/.blitz/**
|
||||
**/.next/**
|
||||
**/dist/**
|
||||
**/.vercel/**
|
||||
**/.test*
|
||||
/examples/auth2
|
||||
|
||||
prettier.config.*
|
||||
jest.config.*
|
||||
jest.setup.*
|
||||
babel.config.*
|
||||
eslint.config.*
|
||||
|
||||
/__mocks__
|
||||
/__fixturse
|
||||
/assets
|
||||
/patches
|
||||
/rfc-docs
|
||||
/scripts
|
||||
/types
|
||||
/recipes/*/templates
|
||||
/packages/generator/templates
|
||||
/packages/cli/lib
|
||||
/packages/babel-preset/src/fix-node-file-trace/tests/**
|
||||
/test/integration/**/out/**
|
||||
/nextjs/packages/create-next-app
|
||||
|
||||
|
||||
// COPIED FROM nextjs/.eslintignore
|
||||
/nextjs/**/.next/**
|
||||
/nextjs/**/_next/**
|
||||
/nextjs/**/dist/**
|
||||
/nextjs/examples/**
|
||||
/nextjs/packages/next/bundles/webpack/packages/*.runtime.js
|
||||
/nextjs/packages/next/compiled/**/*
|
||||
/nextjs/packages/react-refresh-utils/**/*.js
|
||||
/nextjs/packages/react-dev-overlay/lib/**
|
||||
/nextjs/**/__tmp__/**
|
||||
/nextjs/.github/actions/next-stats-action/.work
|
||||
/nextjs/packages/next-codemod/transforms/__testfixtures__/**/*
|
||||
/nextjs/packages/next-codemod/transforms/__tests__/**/*
|
||||
/nextjs/packages/next-codemod/**/*.js
|
||||
/nextjs/packages/next-codemod/**/*.d.ts
|
||||
/nextjs/packages/next-env/**/*.d.ts
|
||||
/nextjs/packages/next/build/swc/tests/fixture/**
|
||||
/nextjs/test/integration/async-modules/**
|
||||
/nextjs/test/integration/eslint/**
|
||||
/nextjs/test/integration/babel/**
|
||||
/nextjs/test-timings.json
|
||||
**/package.json
|
||||
*.d.ts
|
||||
|
||||
119
.eslintrc.js
119
.eslintrc.js
@@ -1,119 +0,0 @@
|
||||
module.exports = {
|
||||
parser: "@babel/eslint-parser",
|
||||
env: {
|
||||
browser: true,
|
||||
commonjs: true,
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 6,
|
||||
requireConfigFile: false,
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
babelOptions: {
|
||||
presets: ["@babel/preset-env", "@babel/preset-react"],
|
||||
caller: {
|
||||
// Eslint supports top level await when a parser for it is included. We enable the parser by default for Babel.
|
||||
supportsTopLevelAwait: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: ["import", "unicorn", "simple-import-sort"],
|
||||
extends: ["react-app"],
|
||||
rules: {
|
||||
"react/react-in-jsx-scope": "off", // React is always in scope with Blitz
|
||||
"jsx-a11y/anchor-is-valid": "off", //Doesn't play well with Blitz/Next <Link> usage
|
||||
"import/first": "off",
|
||||
"import/no-default-export": "error",
|
||||
"require-await": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"unicorn/filename-case": [
|
||||
"error",
|
||||
{
|
||||
case: "kebabCase",
|
||||
},
|
||||
],
|
||||
"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.
|
||||
"^\\.",
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 6,
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
project: `./tsconfig.eslint.json`,
|
||||
},
|
||||
plugins: ["@typescript-eslint"],
|
||||
rules: {
|
||||
"@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"],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["examples/**", "recipes/**"],
|
||||
rules: {
|
||||
"import/no-default-export": "off",
|
||||
"unicorn/filename-case": "off",
|
||||
"@typescript-eslint/no-floating-promises": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["examples/**"],
|
||||
plugins: ["cypress"],
|
||||
parserOptions: {
|
||||
project: null,
|
||||
},
|
||||
env: {
|
||||
"cypress/globals": true,
|
||||
},
|
||||
rules: {
|
||||
"simple-import-sort/imports": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["packages/cli/src/commands/**/*"],
|
||||
rules: {
|
||||
"require-await": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["test/**", "**/__fixtures__/**"],
|
||||
rules: {
|
||||
"import/no-default-export": "off",
|
||||
"require-await": "off",
|
||||
"unicorn/filename-case": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
@@ -2,7 +2,4 @@
|
||||
|
||||
* @flybayer @beerose
|
||||
|
||||
# packages/cli/**/* @aem, @flybayer
|
||||
# packages/generator/**/* @aem @flybayer
|
||||
packages/generator/templates**/* @flybayer
|
||||
# packages/installer/**/* @aem @flybayer
|
||||
|
||||
18
.github/checkInstallTime.js
vendored
18
.github/checkInstallTime.js
vendored
@@ -1,18 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require("fs")
|
||||
const yarnOut = fs.readFileSync(0, {encoding: "utf8"})
|
||||
|
||||
const [installTimeString] = /(?<=^Done in )\d+\.\d+(?=s\.$)/m.exec(yarnOut)
|
||||
const installTime = Number(installTimeString)
|
||||
|
||||
console.log(`Install time: ${installTime}s`)
|
||||
|
||||
if (installTime < 30) {
|
||||
console.log("We're below 30 secs. That's awesome!")
|
||||
} else if (installTime < 50) {
|
||||
console.log("We're below 50 secs. That's fine!")
|
||||
} else {
|
||||
console.log("We're above 50 secs. That's not great!")
|
||||
process.exit(1)
|
||||
}
|
||||
26
.github/workflows/compressed.yml
vendored
26
.github/workflows/compressed.yml
vendored
@@ -1,26 +0,0 @@
|
||||
name: Size Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master, canary]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Compressed Size
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
- name: Count size
|
||||
uses: preactjs/compressed-size-action@v2
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
exclude: "{./nextjs/**}"
|
||||
475
.github/workflows/main.yml
vendored
475
.github/workflows/main.yml
vendored
@@ -11,467 +11,28 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Cache node_modules
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
**/node_modules
|
||||
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.node_version}}-yarn-v14-
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile --silent
|
||||
env:
|
||||
CI: true
|
||||
- name: manypkg lint
|
||||
run: yarn manypkg check
|
||||
env:
|
||||
CI: true
|
||||
- name: Build packages
|
||||
run: yarn build
|
||||
env:
|
||||
CI: true
|
||||
- name: yarn lint
|
||||
run: yarn lint
|
||||
env:
|
||||
CI: true
|
||||
|
||||
build-linux:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 25
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Cache node_modules
|
||||
id: yarn-cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
**/node_modules
|
||||
key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.node_version}}-yarn-v14-
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- name: Build Packages
|
||||
run: yarn build
|
||||
- uses: actions/cache@v2
|
||||
id: cache-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
|
||||
testBlitzPackages:
|
||||
name: Blitz - Test Packages
|
||||
needs: build-linux
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
- name: Setup kernel to increase watchers
|
||||
if: runner.os == 'Linux'
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
- name: Test Blitz Packages
|
||||
run: yarn testonly:packages
|
||||
env:
|
||||
CI: true
|
||||
|
||||
testNextPackages:
|
||||
name: Next - Test Packages
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nextjs
|
||||
needs: build-linux
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
- name: Setup kernel to increase watchers
|
||||
if: runner.os == 'Linux'
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
- name: Test Next Packages
|
||||
run: yarn testonly:packages
|
||||
env:
|
||||
CI: true
|
||||
|
||||
testBlitzExamples:
|
||||
timeout-minutes: 30
|
||||
name: Blitz - Test Example Apps (ubuntu-latest)
|
||||
needs: build-linux
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
DEBUG: blitz:session
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
# Needed to get cypress binary
|
||||
- run: yarn cypress install
|
||||
- name: Install sass
|
||||
run: yarn install -W sass
|
||||
- name: Setup kernel to increase watchers
|
||||
if: runner.os == 'Linux'
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
- name: Test examples
|
||||
run: yarn testonly:examples
|
||||
env:
|
||||
CI: true
|
||||
|
||||
testBlitzExamplesWin:
|
||||
timeout-minutes: 30
|
||||
name: Blitz - Test Example Apps (windows-latest)
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 25
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
|
||||
# - name: Get yarn cache directory path
|
||||
# id: yarn-cache-dir-path
|
||||
# run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
# - name: Cache node_modules
|
||||
# id: yarn-cache
|
||||
# uses: actions/cache@v2
|
||||
# with:
|
||||
# path: |
|
||||
# ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
# **/node_modules
|
||||
# key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- name: Build Packages
|
||||
run: yarn build
|
||||
# Needed to get cypress binary
|
||||
- run: yarn cypress install
|
||||
- name: Install sass
|
||||
run: yarn install -W sass
|
||||
- name: Test examples
|
||||
run: yarn testonly:examples
|
||||
env:
|
||||
CI: true
|
||||
|
||||
checkPrecompiled:
|
||||
name: Check Pre-compiled
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nextjs
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-linux
|
||||
env:
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
- run: ./scripts/check-pre-compiled.sh
|
||||
|
||||
testUnit:
|
||||
name: Nextjs - Test Unit
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nextjs
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-linux
|
||||
env:
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
NEXT_TEST_JOB: 1
|
||||
HEADLESS: true
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
- run: node run-tests.js --type unit -g 1/1
|
||||
|
||||
testIntegrationBlitz:
|
||||
name: Blitz - Test Integration
|
||||
needs: build-linux
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
NEXT_TEST_JOB: 1
|
||||
HEADLESS: true
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
|
||||
# TODO: remove after we fix watchpack watching too much
|
||||
- name: Setup kernel to increase watchers
|
||||
if: runner.os == 'Linux'
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
|
||||
- run: xvfb-run node nextjs/run-tests.js -c 3
|
||||
|
||||
testIntegrationBlitzWin:
|
||||
name: Blitz - Test Integration (Windows)
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
NEXT_TEST_JOB: 1
|
||||
HEADLESS: true
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 25
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
|
||||
# - name: Get yarn cache directory path
|
||||
# id: yarn-cache-dir-path
|
||||
# run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
# - name: Cache node_modules
|
||||
# id: yarn-cache
|
||||
# uses: actions/cache@v2
|
||||
# with:
|
||||
# path: |
|
||||
# ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
# **/node_modules
|
||||
# key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- name: Build Packages
|
||||
run: yarn build
|
||||
- run: node nextjs/run-tests.js
|
||||
|
||||
testIntegration:
|
||||
name: Nextjs - Test Integration
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nextjs
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-linux
|
||||
env:
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
NEXT_TEST_JOB: 1
|
||||
HEADLESS: true
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
group: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
# TODO: remove after we fix watchpack watching too much
|
||||
- name: Setup kernel to increase watchers
|
||||
if: runner.os == 'Linux'
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
- run: xvfb-run node run-tests.js -g ${{ matrix.group }}/20 -c 3
|
||||
|
||||
testIntegrationWin:
|
||||
name: Nextjs - Test Integration (Windows)
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
NEXT_TEST_JOB: 1
|
||||
HEADLESS: true
|
||||
os:
|
||||
- ubuntu-latest
|
||||
node_version:
|
||||
- 16
|
||||
name: Node ${{ matrix.node_version }} - ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: pnpm/action-setup@646cdf48217256a3d0b80361c5a50727664284f2
|
||||
with:
|
||||
fetch-depth: 25
|
||||
- name: Use Node.js
|
||||
version: 6.32.6
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "14"
|
||||
- run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
|
||||
# - name: Get yarn cache directory path
|
||||
# id: yarn-cache-dir-path
|
||||
# run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
# - name: Cache node_modules
|
||||
# id: yarn-cache
|
||||
# uses: actions/cache@v2
|
||||
# with:
|
||||
# path: |
|
||||
# ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
# **/node_modules
|
||||
# key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }}
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-
|
||||
- run: yarn install --frozen-lockfile --check-files
|
||||
- name: Build Packages
|
||||
run: yarn build
|
||||
- run: node run-tests.js test/integration/basic/test/index.test.js test/integration/production/test/index.test.js test/integration/multi-pages/test/index.test.js
|
||||
working-directory: nextjs
|
||||
|
||||
testElectron:
|
||||
name: Nextjs - Test Electron
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nextjs
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-linux
|
||||
env:
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
NEXT_TEST_JOB: 1
|
||||
HEADLESS: true
|
||||
TEST_ELECTRON: 1
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
|
||||
# TODO: remove after we fix watchpack watching too much
|
||||
- name: Setup kernel to increase watchers
|
||||
run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
|
||||
- run: cd test/integration/with-electron/app && yarn
|
||||
- run: xvfb-run node run-tests.js test/integration/with-electron/test/index.test.js
|
||||
|
||||
testsPass:
|
||||
name: thank you, next
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
[
|
||||
checkPrecompiled,
|
||||
testIntegration,
|
||||
testIntegrationBlitz,
|
||||
testIntegrationBlitzWin,
|
||||
testUnit,
|
||||
testBlitzPackages,
|
||||
testNextPackages,
|
||||
testBlitzExamples,
|
||||
testBlitzExamplesWin,
|
||||
]
|
||||
steps:
|
||||
- run: exit 0
|
||||
|
||||
testFirefox:
|
||||
name: Nextjs - Test Firefox (production)
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nextjs
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-linux
|
||||
env:
|
||||
HEADLESS: true
|
||||
BROWSER_NAME: "firefox"
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
- run: node run-tests.js -c 1 test/integration/production/test/index.test.js
|
||||
|
||||
testSafari:
|
||||
name: Nextjs - Test Safari (production)
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nextjs
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-linux
|
||||
env:
|
||||
BROWSERSTACK: true
|
||||
BROWSER_NAME: "safari"
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
SKIP_LOCAL_SELENIUM_SERVER: true
|
||||
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
|
||||
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
- run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || node run-tests.js -c 1 test/integration/production/test/index.test.js'
|
||||
|
||||
testSafariOld:
|
||||
name: Nextjs - Test Safari 10.1 (nav)
|
||||
defaults:
|
||||
run:
|
||||
working-directory: nextjs
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-linux, testSafari]
|
||||
env:
|
||||
BROWSERSTACK: true
|
||||
LEGACY_SAFARI: true
|
||||
BROWSERNAME: "safari"
|
||||
BLITZ_TELEMETRY_DISABLED: 1
|
||||
SKIP_LOCAL_SELENIUM_SERVER: true
|
||||
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
|
||||
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
|
||||
steps:
|
||||
- uses: actions/cache@v2
|
||||
id: restore-build
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ runner.os }}-${{ github.sha }}
|
||||
- run: '[[ -z "$BROWSERSTACK_ACCESS_KEY" ]] && echo "Skipping for PR" || node run-tests.js test/integration/production-nav/test/index.test.js'
|
||||
node-version: ${{ matrix.node_version }}
|
||||
cache: "pnpm"
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm manypkg check
|
||||
- run: pnpm build
|
||||
- run: pnpm lint
|
||||
- run: pnpm build:apps
|
||||
- run: pnpm test
|
||||
|
||||
34
.gitignore
vendored
34
.gitignore
vendored
@@ -1,3 +1,37 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
coverage
|
||||
|
||||
.next/
|
||||
out/
|
||||
build
|
||||
dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# turbo
|
||||
.turbo
|
||||
|
||||
.log
|
||||
.DS_Store
|
||||
.idea
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn lint-staged
|
||||
yarn pretty-quick --staged
|
||||
pnpm manypkg check
|
||||
pnpm lint
|
||||
pnpm pretty-quick --staged
|
||||
|
||||
@@ -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", "beerose", "depfu"]
|
||||
@@ -1 +1 @@
|
||||
14.18.1
|
||||
16.13.2
|
||||
|
||||
16
.npmignore
16
.npmignore
@@ -1,16 +0,0 @@
|
||||
.DS_Store
|
||||
.prettierrc
|
||||
.nyc_output
|
||||
.travis.yml
|
||||
coverage
|
||||
coverage.lcov
|
||||
bench
|
||||
docs
|
||||
src
|
||||
examples
|
||||
babel.config.js
|
||||
test
|
||||
CONTRIBUTING.md
|
||||
CODE_OF_CONDUCT.md
|
||||
*.ts
|
||||
!*.d.ts
|
||||
6
.npmrc
6
.npmrc
@@ -1 +1,7 @@
|
||||
save-exact=true
|
||||
|
||||
public-hoist-pattern[]=secure-password
|
||||
public-hoist-pattern[]=*types*
|
||||
public-hoist-pattern[]=*eslint*
|
||||
public-hoist-pattern[]=@prettier/plugin-*
|
||||
public-hoist-pattern[]=*prettier-plugin-*
|
||||
|
||||
@@ -16,10 +16,5 @@ reports
|
||||
tsconfig.tsbuildinfo
|
||||
dist
|
||||
bin
|
||||
!packages/blitz/src/bin
|
||||
packages/generator/templates/**
|
||||
.github/ISSUE_TEMPLATE/bug_report.md
|
||||
nextjs/packages/next/compiled/**
|
||||
|
||||
// Because file from nextjs upstream isn't formatted properly
|
||||
nextjs/packages/next/build/webpack-config.ts
|
||||
packages/generator/templates/**
|
||||
|
||||
@@ -2,90 +2,18 @@
|
||||
|
||||
[Read the Contributing Guide at Blitzjs.com](https://blitzjs.com/docs/contributing)
|
||||
|
||||
## Notes For Core Team
|
||||
## To run tests
|
||||
|
||||
### To Publish a new NPM Package under `@blitzjs/` namespace
|
||||
Make sure you have `chromedriver` installed for your Chrome version. You can install it with
|
||||
|
||||
1. cd into the package directory
|
||||
2. Run `npm publish --tag danger --access public`
|
||||
- `--access public` is required because scoped packages are set to private by default
|
||||
- `brew install --cask chromedriver` on Mac OS X
|
||||
- `chocolatey install chromedriver` on Windows
|
||||
- Or manually download the version that matches your installed chrome version (if there's no match, download a version under it, but not above) from the [chromedriver repo](https://chromedriver.storage.googleapis.com/index.html) and add the binary to `<next-repo>/node_modules/.bin`
|
||||
|
||||
### Syncing Next.js Fork
|
||||
You may also have to [install Rust](https://www.rust-lang.org/tools/install) and build our native packages to see all tests pass locally. We check in binaries for the most common targets and those required for CI so that most people don't have to, but if you do not see a binary for your target in `packages/next/native`, you can build it by running `yarn --cwd packages/next build-native`. If you are working on the Rust code and you need to build the binaries for ci, you can manually trigger [the workflow](https://github.com/vercel/next.js/actions/workflows/build_native.yml) to build and commit with the "Run workflow" button.
|
||||
|
||||
1. Run `yarn push-nextjs`
|
||||
- If it fails with an error of `git-subrepo: Can't commit: 'subrepo/nextjs' doesn't contain upstream HEAD:`, then run `yarn push-nextjs --force` (see https://github.com/ingydotnet/git-subrepo/issues/530)
|
||||
2. Create new git branch for the upgrade
|
||||
3. In the forked repo (https://github.com/blitz-js/next.js), run:
|
||||
1. `git pull`
|
||||
2. `git fetch --all`
|
||||
3. `git merge v10.2.0` (change the version to be the version you are updating to)
|
||||
4. Run `rm -rf examples && git add examples`
|
||||
5. To resolve conflict with their version for a path, like docs, run this:
|
||||
- `git checkout --theirs docs && git add docs`
|
||||
6. Resolve all merge conflicts and complete merge
|
||||
7. Run `yarn` and make sure all builds complete
|
||||
8. Run `yarn lint` and fix any issues
|
||||
9. Commit all changes to finish merge
|
||||
10. `git push`
|
||||
4. Run `yarn pull-nextjs`
|
||||
5. Run `yarn`
|
||||
6. Run `yarn manypkg check` and optionally `yarn manypkg fix` to fix any issues
|
||||
7. Under `nextjs/`, run `./scripts/check-pre-compiled.sh` and commit the changes
|
||||
8. Run `yarn build:nextjs`
|
||||
9. Run `yarn lint` - fix any issues
|
||||
10. Run `yarn build` - fix any issues
|
||||
11. Run `yarn test:nextjs-size` and update tests if there are any failures
|
||||
12. Open PR and fix any failing tests
|
||||
13. Update any references to nextjs in new code including imports like `next/image`, etc.
|
||||
14. Any doc updates needed?
|
||||
15. Merge PR
|
||||
16. `yarn push-nextjs`
|
||||
|
||||
#### Troubleshooting
|
||||
|
||||
##### yarn lint - Failed to load parser
|
||||
|
||||
Caused by invalid version of `@babel/eslint-parser`. `7.13.14` is a working version. I think it may be an incompatibility between this version and the version of eslint?
|
||||
|
||||
- change version of eslint-parser
|
||||
- run `yarn --check-files`
|
||||
- run `./scripts/check-pre-compiled.sh` from `./nextjs/`
|
||||
- run `yarn build:nextjs` from root
|
||||
- Try linting again
|
||||
Running all tests:
|
||||
|
||||
```
|
||||
~/c/blitz> yarn lint
|
||||
yarn run v1.22.10
|
||||
$ eslint --ext ".js,.ts,.tsx" .
|
||||
|
||||
Oops! Something went wrong! :(
|
||||
|
||||
ESLint: 7.21.0
|
||||
|
||||
Error: Failed to load parser './parser.js' declared in 'examples/auth/.eslintrc.js » eslint-config-blitz » eslint-config-next': Cannot find module '@babel/parser'
|
||||
at webpackEmptyContext (/Users/b/c/blitz/nextjs/packages/next/dist/compiled/babel/bundle.js:1:33258)
|
||||
at Object.73139 (/Users/b/c/blitz/nextjs/packages/next/dist/compiled/babel/bundle.js:2194:783181)
|
||||
at __nccwpck_require__ (/Users/b/c/blitz/nextjs/packages/next/dist/compiled/babel/bundle.js:2194:1065271)
|
||||
at Object.eslintParser (/Users/b/c/blitz/nextjs/packages/next/dist/compiled/babel/bundle.js:1:43676)
|
||||
at Object.<anonymous> (/Users/b/c/blitz/nextjs/packages/next/dist/compiled/babel/eslint-parser.js:1:100)
|
||||
at Module._compile (/Users/b/c/blitz/node_modules/v8-compile-cache/v8-compile-cache.js:192:30)
|
||||
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
|
||||
at Module.load (internal/modules/cjs/loader.js:863:32)
|
||||
at Function.Module._load (internal/modules/cjs/loader.js:708:14)
|
||||
at Module.require (internal/modules/cjs/loader.js:887:19)
|
||||
error Command failed with exit code 2.
|
||||
```
|
||||
|
||||
##### Failed to compile - LICENSE
|
||||
|
||||
This error occurs sometimes when you import code from packages/next/build/utils.ts into some other code like config-shared.ts. Solution is to move the code into another file.
|
||||
|
||||
```
|
||||
Failed to compile.
|
||||
../../../packages/next/dist/compiled/webpack/LICENSE
|
||||
Module parse failed: Unexpected token (1:10)
|
||||
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See webpack.js.org/concepts#loaders
|
||||
> Copyright JS Foundation and other contributors
|
||||
|
|
||||
| Permission is hereby granted, free of charge, to any person obtaining
|
||||
```sh
|
||||
pnpm test
|
||||
```
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Brandon Bayer
|
||||
Copyright (c) 2022 Blitz Revolution Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
124
MEETING_NOTES.md
124
MEETING_NOTES.md
@@ -1,124 +0,0 @@
|
||||
# 2020-08-17 Blitz Contributor Call
|
||||
|
||||
- Attending: Brandon Bayer, Adam Markon, Kellen Mace, Myron Davis, Dwight Watson
|
||||
- Brandon:
|
||||
- Auth out, set up by default in canary release
|
||||
- Need to work on a potential CSRF bug
|
||||
- Next major release will include auth by default and allow you to choose your form library
|
||||
- Next auth features are email confirmation
|
||||
- After that, logging and plugins are next
|
||||
- Adam:
|
||||
- Overhauled the recipe infrastructure. Now using jscodeshift instead of recast
|
||||
- Added support for conditional JSX in templates
|
||||
- Going to work on custom templates next
|
||||
- Dwight
|
||||
- Has been opening issues for problems
|
||||
- Made a few PRs for some issues
|
||||
|
||||
# 2020-07-07 Blitz Contributor Call
|
||||
|
||||
- Attending: Brandon Bayer, Robert Rosenburg, Jeremy Liberman
|
||||
- Brandon:
|
||||
- Finishing up session managment code
|
||||
- Waiting for review of session managment code from Rishabh
|
||||
- Will be working on actual auth code (vs session management) like password hashing, password reset, social login, etc.
|
||||
- Benefits of our session managment vs rails
|
||||
- Don’t have to redirect to login page
|
||||
- Using top level error component that catches authentication errors
|
||||
- You can login from anywhere, during sign up
|
||||
- Robert:
|
||||
- Waiting on Kirstina’s website designs. Desktop design is finished, but she's working on mobile design
|
||||
- Code snippet / sandbox of blitz code for the website
|
||||
|
||||
# 2020-06-23 Blitz Contributor Call
|
||||
|
||||
- Attending: Brandon Bayer, Robert Rosenberg, Justin Hall, Adam Markon
|
||||
- Brandon:
|
||||
- Server side session management code is mostly set up. Still need to integrate client side of RPC calls to expose session information
|
||||
- Identity verification/Oauth integrations still need to be firmed up
|
||||
- Once auth is wrapped up we should be ready to start on plugins
|
||||
- Adam:
|
||||
- Been experimenting with smart page generation based on the current schema model
|
||||
- MDX installer recipes
|
||||
- Robert:
|
||||
- Waiting on designs from Kristina
|
||||
- Prism component from theme ui customization? If not try and grab source code from docusaurus line highlighting component
|
||||
- Justin:
|
||||
- Updated the tutorial
|
||||
- Helped with various bug fixes
|
||||
|
||||
# 2020-06-17 Blitz Contributor Call
|
||||
|
||||
- Attending: Brandon Bayer, Fran Zekan, Sigurd Wahl
|
||||
- Brandon:
|
||||
- Spent the past week implementing HTTP middlware which is now released!
|
||||
- Now working on implementing session management!
|
||||
- Fran:
|
||||
- Finishing up the PR for adding `blitz db seed`
|
||||
- Sigurd:
|
||||
- New to the community, slowly learning the codebase
|
||||
- Exicited to get a first PR in here sometime soon :)
|
||||
|
||||
# 2020-06-09 Blitz Contributor Call
|
||||
|
||||
- Attending: Brandon Bayer, Rudi Yardley, Fran Zekan, Adam Markon, Robert Rosenberg, Kristina Matuska
|
||||
- Brandon:
|
||||
- blitzjs.com published, docs + marketing site v0.1 live
|
||||
- For now most of the docs copied from react-query and Next, we should eventually clean them up so they're stylistically similar to ours, but really easy to start
|
||||
- HTTP middleware is almost done, just fixing a few edge cases
|
||||
- Authentication is up next after that, translating pseudocode into actual code
|
||||
- Kristina:
|
||||
- Homepage design mostly wrapped up right now, have to finish up mobile and light mode and then ready to move on to the rest of the docs
|
||||
- Syntax highlighting, we can customize colors
|
||||
- Sidebar inspiration from tailwindcss
|
||||
- Robert:
|
||||
- Decided to wait to convert existing components to theme UI until the final design is done
|
||||
- More docs content:
|
||||
- More guides
|
||||
- Improve the tutorial to incorporate relationship generation
|
||||
- Add branches for canary and master
|
||||
- If you add a feature you can add your documentation to the canary branch
|
||||
- Adam:
|
||||
- blitz generate model finished!
|
||||
- Installer rewrite complete
|
||||
- At similar place to what Gatsby has for installing stuff
|
||||
- Next up:
|
||||
- Support gatsby MDX recipes
|
||||
- Make all code generators aware of actual model attributes
|
||||
- Fran:
|
||||
- Working on a package for getting the blitz config anywhere - getConfig()
|
||||
- Prevent app from "warming" the server when deployed as server rather than serverless
|
||||
- Testing examples - e2e with cypress and unit tests with Jest so we can link to a testing setup in the docs/getting started guide
|
||||
- Rudi:
|
||||
- Extracted out the @blitzjs/file-pipeline (previously synchronizer)
|
||||
- Extracted out the @blitzjs/display package
|
||||
- Working on various Next.js compatibility issues
|
||||
- Debugging a bug in blitz start where it gets stuck at \_manifest.json
|
||||
|
||||
# 2020-05-26 Blitz Contributor Call
|
||||
|
||||
- Attending: Brandon Bayer, Robert Rosenberg, Adam Markon, Simon Debbarma
|
||||
- Brandon:
|
||||
- Kitze livestream last week went great — recording on youtube
|
||||
- Codebase walkthrough yesterday went great — recording on youtube
|
||||
- Website overhaul, installed Theme UI
|
||||
- Adam:
|
||||
- Opened PR for Prisma model generation from the CLI
|
||||
- Working on Installer stuff and prepping for integration with Gatsby recipes
|
||||
- Simon
|
||||
- Working on custom illustrations for the web
|
||||
- Robert
|
||||
- Misc work on website
|
||||
- Website
|
||||
- Most website components are owned by us now instead of docusaurus, we'll need to be weary of api updates and any other important component updates
|
||||
- Make sidebar like tailwind docs sidebar
|
||||
- Dark theme needs to be fixed
|
||||
- Theme switcher inconsistent
|
||||
- Live code sandbox examples
|
||||
- Code comparison between blitz and rails
|
||||
- Auth
|
||||
- Rishabh continuing to work on pseudo code for the session management library
|
||||
- Brandon planning to build http middleware support this week
|
||||
- CLI
|
||||
- Adam working on new features, including generating prisma models and making existing templates aware of actual model attributes
|
||||
- Plugin ideas / discussion once recipes are farther along. Will post an RFC for plugins at some point
|
||||
25
README.md
25
README.md
@@ -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-388-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-381-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">
|
||||
@@ -109,6 +109,12 @@ Your financial contributions help ensure Blitz continues to be developed and mai
|
||||
<tr>
|
||||
<td><a aria-label="Andreas Asprou" href="https://andreas.fyi">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/andreas.jpg" width="40px"/>
|
||||
</a></td>
|
||||
<td><a aria-label="Digas" href="https://digsas.com">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/digsas.svg" width="40px"/>
|
||||
</a></td>
|
||||
<td><a aria-label="userTrack" href="https://www.usertrack.net/?ref=blitzjs">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/usertrack.png" width="40px"/>
|
||||
</a></td>
|
||||
<td><a aria-label="MeetKai" href="https://meetkai.com/?ref=blitzjs">
|
||||
<img alt="" src="https://raw.githubusercontent.com/blitz-js/blitz/canary/assets/meetkai.png" width="40px"/>
|
||||
@@ -228,14 +234,6 @@ _Issue triage, pull request triage, community encouragement and moderation, etc_
|
||||
</sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://www.saheen.me/">
|
||||
<img src="https://avatars.githubusercontent.com/shellord" width="100px;" alt="Saheen Shoukath avatar" /><br />
|
||||
<sub>
|
||||
<b>Saheen Shoukath</b>
|
||||
</sub>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- markdownlint-enable -->
|
||||
@@ -742,15 +740,6 @@ Thanks to these wonderful people ([emoji key](https://allcontributors.org/docs/e
|
||||
<td align="center"><a href="https://github.com/c-ciobanu"><img src="https://avatars.githubusercontent.com/u/33382714?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cristi Ciobanu</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=c-ciobanu" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://arpitdalal.dev"><img src="https://avatars.githubusercontent.com/u/61059807?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Arpit Dalal</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=arpitdalal" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/robertrisch"><img src="https://avatars.githubusercontent.com/u/73828816?v=4?s=100" width="100px;" alt=""/><br /><sub><b>robertrisch</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=robertrisch" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/dineshgadge"><img src="https://avatars.githubusercontent.com/u/186976?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dinesh Gadge</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=dineshgadge" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/maltekiessling"><img src="https://avatars.githubusercontent.com/u/30420110?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Malte Kießling</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=maltekiessling" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="ospfranco.com"><img src="https://avatars.githubusercontent.com/u/1634213?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Oscar Franco</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ospfranco" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="adamtrager.com"><img src="https://avatars.githubusercontent.com/u/1719791?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Adam Trager</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=Nfinished" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://saheen.codes"><img src="https://avatars.githubusercontent.com/u/2632896?v=4?s=100" width="100px;" alt=""/><br /><sub><b>saheenshoukath</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=shellord" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://jahneer.me"><img src="https://avatars.githubusercontent.com/u/54552763?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Husnul Jahneer</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=husnuljahneer" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/ReykCS"><img src="https://avatars.githubusercontent.com/u/40463716?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Reyk</b></sub></a><br /><a href="https://github.com/blitz-js/blitz/commits?author=ReykCS" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -7,4 +7,3 @@ TODO
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Email Brandon Bayer at b@bayer.ws to report a vulnerablity, and he will follow up with you asap.
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
const {fs} = require("memfs")
|
||||
|
||||
module.exports = fs
|
||||
1
apps/web/.eslintrc.js
Normal file
1
apps/web/.eslintrc.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require("@blitzjs/config/eslint")
|
||||
4
apps/web/.gitignore
vendored
Normal file
4
apps/web/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
# Keep environment variables out of version control
|
||||
.env
|
||||
*.sqlite
|
||||
@@ -1,12 +1,8 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
@@ -29,6 +25,6 @@ You can check out [the Next.js GitHub repository](https://github.com/vercel/next
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_source=github.com&utm_medium=referral&utm_campaign=turborepo-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
20
apps/web/app/blitz-client.ts
Normal file
20
apps/web/app/blitz-client.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import {AuthClientPlugin} from "@blitzjs/auth"
|
||||
import {setupClient} from "@blitzjs/next"
|
||||
import {BlitzRpcPlugin} from "@blitzjs/rpc"
|
||||
|
||||
const {withBlitz} = setupClient({
|
||||
plugins: [
|
||||
AuthClientPlugin({
|
||||
cookiePrefix: "webapp-cookie-prefix",
|
||||
}),
|
||||
BlitzRpcPlugin({
|
||||
reactQueryOptions: {
|
||||
queries: {
|
||||
staleTime: 7000,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
export {withBlitz}
|
||||
17
apps/web/app/blitz-server.ts
Normal file
17
apps/web/app/blitz-server.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import {setupBlitz} from "@blitzjs/next"
|
||||
import {AuthServerPlugin, PrismaStorage} from "@blitzjs/auth"
|
||||
import {prisma as db} from "../prisma/index"
|
||||
import {simpleRolesIsAuthorized} from "@blitzjs/auth"
|
||||
|
||||
const {gSSP, gSP, api} = setupBlitz({
|
||||
plugins: [
|
||||
AuthServerPlugin({
|
||||
cookiePrefix: "webapp-cookie-prefix",
|
||||
// TODO fix type
|
||||
storage: PrismaStorage(db as any),
|
||||
isAuthorized: simpleRolesIsAuthorized,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
export {gSSP, gSP, api}
|
||||
14
apps/web/app/mutations/createUser.ts
Normal file
14
apps/web/app/mutations/createUser.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {Ctx} from "blitz"
|
||||
import {prisma} from "../../prisma"
|
||||
import {User} from "prisma"
|
||||
|
||||
export default async function createUser(
|
||||
input: {name: string; email: string},
|
||||
ctx: Ctx,
|
||||
): Promise<User> {
|
||||
ctx.session.$authorize()
|
||||
|
||||
const user = await prisma.user.create({data: {name: input.name, email: input.email}})
|
||||
|
||||
return user
|
||||
}
|
||||
4
apps/web/app/mutations/setBasic.ts
Normal file
4
apps/web/app/mutations/setBasic.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default function setBasic(input, ctx) {
|
||||
console.log("SET BASIC input", input)
|
||||
return
|
||||
}
|
||||
5
apps/web/app/queries/getBasic.ts
Normal file
5
apps/web/app/queries/getBasic.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default async function getBasic(input, ctx) {
|
||||
console.log("INPUT", input)
|
||||
|
||||
return "basic-result"
|
||||
}
|
||||
11
apps/web/app/queries/getUsers.ts
Normal file
11
apps/web/app/queries/getUsers.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import {Ctx} from "blitz"
|
||||
import {prisma} from "../../prisma"
|
||||
import {User} from "prisma"
|
||||
|
||||
export default async function getUsers(_input: {}, ctx: Ctx): Promise<User[]> {
|
||||
ctx.session.$authorize()
|
||||
|
||||
const users = await prisma.user.findMany()
|
||||
|
||||
return users
|
||||
}
|
||||
5
apps/web/app/queries/v2/getV2Basic.ts
Normal file
5
apps/web/app/queries/v2/getV2Basic.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default function getV2Basic(input, ctx) {
|
||||
console.log("INPUT", input)
|
||||
|
||||
return "basic-result"
|
||||
}
|
||||
11
apps/web/jest.config.js
Normal file
11
apps/web/jest.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const nextJest = require("@blitzjs/next/jest")
|
||||
|
||||
const createJestConfig = nextJest({
|
||||
dir: "./",
|
||||
})
|
||||
|
||||
const customJestConfig = {
|
||||
testEnvironment: "jest-environment-jsdom",
|
||||
}
|
||||
|
||||
module.exports = createJestConfig(customJestConfig)
|
||||
@@ -1,5 +1,4 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
10
apps/web/next.config.js
Normal file
10
apps/web/next.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const withBundleAnalyzer = require("@next/bundle-analyzer")({
|
||||
enabled: process.env.ANALYZE === "true",
|
||||
})
|
||||
const {withBlitz} = require("@blitzjs/next")
|
||||
|
||||
module.exports = withBlitz(
|
||||
withBundleAnalyzer({
|
||||
reactStrictMode: true,
|
||||
}),
|
||||
)
|
||||
36
apps/web/package.json
Normal file
36
apps/web/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start:dev": "pnpm run prisma:start && next dev",
|
||||
"buildapp": "prisma generate && next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
|
||||
"prisma:start": "prisma generate && prisma migrate deploy",
|
||||
"prisma:studio": "prisma studio",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzjs/auth": "workspace:*",
|
||||
"@blitzjs/config": "workspace:*",
|
||||
"@blitzjs/next": "workspace:*",
|
||||
"@blitzjs/rpc": "workspace:*",
|
||||
"@prisma/client": "3.9.0",
|
||||
"@types/jest": "27.4.1",
|
||||
"blitz": "workspace:*",
|
||||
"jest": "27.5.1",
|
||||
"next": "12.1.1",
|
||||
"prisma": "3.9.0",
|
||||
"react": "18.0.0",
|
||||
"react-dom": "18.0.0",
|
||||
"ts-node": "10.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "12.0.8",
|
||||
"@types/react": "17.0.43",
|
||||
"eslint": "7.32.0",
|
||||
"typescript": "^4.5.3"
|
||||
}
|
||||
}
|
||||
37
apps/web/pages/_app.tsx
Normal file
37
apps/web/pages/_app.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import {ErrorFallbackProps, ErrorComponent, ErrorBoundary} from "@blitzjs/next"
|
||||
import {AuthenticationError, AuthorizationError} from "blitz"
|
||||
import type {AppProps} from "next/app"
|
||||
import React, {Suspense} from "react"
|
||||
import {withBlitz} from "app/blitz-client"
|
||||
|
||||
function RootErrorFallback({error}: ErrorFallbackProps) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
return <div>Error: You are not authenticated</div>
|
||||
} else if (error instanceof AuthorizationError) {
|
||||
return (
|
||||
<ErrorComponent
|
||||
statusCode={error.statusCode}
|
||||
title="Sorry, you are not authorized to access this"
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ErrorComponent
|
||||
statusCode={(error as any)?.statusCode || 400}
|
||||
title={error.message || error.name}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function MyApp({Component, pageProps}: AppProps) {
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={RootErrorFallback}>
|
||||
<Suspense fallback="Loading...">
|
||||
<Component {...pageProps} />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
export default withBlitz(MyApp)
|
||||
19
apps/web/pages/api/get-user.ts
Normal file
19
apps/web/pages/api/get-user.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import {api} from "app/blitz-server"
|
||||
import {SessionContext} from "@blitzjs/auth"
|
||||
import {prisma} from "../../prisma/index"
|
||||
|
||||
export default api(async (_req, res, ctx) => {
|
||||
const blitzContext = ctx
|
||||
|
||||
const publicData = blitzContext.session.$publicData
|
||||
|
||||
const sessions = await prisma.session.findMany({})
|
||||
const sessionsCount = await prisma.session.count({})
|
||||
|
||||
res.status(200).json({
|
||||
userId: blitzContext.session.userId,
|
||||
publicData: {...publicData},
|
||||
activeSessions: sessions,
|
||||
sessionsCount,
|
||||
})
|
||||
})
|
||||
11
apps/web/pages/api/is-authorized.ts
Normal file
11
apps/web/pages/api/is-authorized.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import {api} from "app/blitz-server"
|
||||
|
||||
export default api(async (_req, res, ctx) => {
|
||||
const {session} = ctx
|
||||
|
||||
const authorized = session.$isAuthorized()
|
||||
|
||||
res.status(200).json({
|
||||
authorized,
|
||||
})
|
||||
})
|
||||
9
apps/web/pages/api/logout.ts
Normal file
9
apps/web/pages/api/logout.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import {api} from "app/blitz-server"
|
||||
|
||||
export default api(async (_req, res, ctx) => {
|
||||
const blitzContext = ctx
|
||||
|
||||
await blitzContext.session.$revoke()
|
||||
|
||||
res.status(200).json({userId: blitzContext.session.userId})
|
||||
})
|
||||
21
apps/web/pages/api/multiply.ts
Normal file
21
apps/web/pages/api/multiply.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import {NextApiRequest, NextApiResponse} from "next"
|
||||
import {prisma} from "../../prisma/index"
|
||||
|
||||
export default async function handle(req: NextApiRequest, res: NextApiResponse) {
|
||||
const session = await prisma.session.findFirst({
|
||||
where: {
|
||||
handle: "test",
|
||||
},
|
||||
})
|
||||
|
||||
const isAuthorized = !!session
|
||||
|
||||
if (!isAuthorized) {
|
||||
res.status(404).json({message: "No access"})
|
||||
return
|
||||
}
|
||||
|
||||
const x = parseInt(req.query.x as string)
|
||||
const y = parseInt(req.query.y as string)
|
||||
res.json({result: x * y})
|
||||
}
|
||||
12
apps/web/pages/api/revoke-all-sessions.ts
Normal file
12
apps/web/pages/api/revoke-all-sessions.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import {api} from "app/blitz-server"
|
||||
import {prisma} from "../../prisma/index"
|
||||
|
||||
export default api(async (_req, res) => {
|
||||
const sessions = await prisma.session.deleteMany()
|
||||
const sessionsCount = await prisma.session.count()
|
||||
|
||||
res.status(200).json({
|
||||
activeSessions: sessions,
|
||||
sessionsCount,
|
||||
})
|
||||
})
|
||||
4
apps/web/pages/api/rpc/[[...blitz]].ts
Normal file
4
apps/web/pages/api/rpc/[[...blitz]].ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import {rpcHandler} from "@blitzjs/rpc"
|
||||
import {api} from "app/blitz-server"
|
||||
|
||||
export default api(rpcHandler({onError: console.log}))
|
||||
17
apps/web/pages/api/set-public-data.ts
Normal file
17
apps/web/pages/api/set-public-data.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import {setPublicDataForUser} from "@blitzjs/auth"
|
||||
import {api} from "app/blitz-server"
|
||||
import {prisma} from "../../prisma/index"
|
||||
|
||||
export default api(async (req, res, ctx) => {
|
||||
if (ctx.session.$thisIsAuthorized()) {
|
||||
ctx.session.$publicData
|
||||
|
||||
await prisma.user.update({
|
||||
where: {id: ctx.session.userId as number},
|
||||
data: {role: req.query.role as string},
|
||||
})
|
||||
await setPublicDataForUser(ctx.session.userId, {role: req.query.role as string})
|
||||
}
|
||||
|
||||
res.status(200).json({userId: ctx.session.userId, role: req.query.role as string})
|
||||
})
|
||||
31
apps/web/pages/api/signin.ts
Normal file
31
apps/web/pages/api/signin.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {api} from "app/blitz-server"
|
||||
import {prisma} from "../../prisma/index"
|
||||
import {SecurePassword} from "@blitzjs/auth"
|
||||
|
||||
export const authenticateUser = async (email: string, password: string) => {
|
||||
const user = await prisma.user.findFirst({where: {email}})
|
||||
if (!user) throw new Error("Authentication Error")
|
||||
|
||||
const result = await SecurePassword.verify(user.hashedPassword, password)
|
||||
|
||||
if (result === SecurePassword.VALID_NEEDS_REHASH) {
|
||||
// Upgrade hashed password with a more secure hash
|
||||
const improvedHash = await SecurePassword.hash(password)
|
||||
await prisma.user.update({where: {id: user.id}, data: {hashedPassword: improvedHash}})
|
||||
}
|
||||
|
||||
const {hashedPassword, ...rest} = user
|
||||
return rest
|
||||
}
|
||||
|
||||
export default api(async (req, res, ctx) => {
|
||||
const blitzContext = ctx
|
||||
|
||||
const user = await authenticateUser(req.query.email as string, req.query.password as string)
|
||||
|
||||
await blitzContext.session.$create({
|
||||
userId: user.id,
|
||||
})
|
||||
|
||||
res.status(200).json({email: req.query.email, userId: blitzContext.session.userId})
|
||||
})
|
||||
22
apps/web/pages/api/signup.ts
Normal file
22
apps/web/pages/api/signup.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import {api} from "app/blitz-server"
|
||||
import {prisma} from "../../prisma/index"
|
||||
import {SecurePassword} from "@blitzjs/auth"
|
||||
|
||||
export default api(async (req, res, ctx) => {
|
||||
const blitzContext = ctx
|
||||
|
||||
const hashedPassword = await SecurePassword.hash(
|
||||
(req.query.password as string) || "test-password",
|
||||
)
|
||||
const email = (req.query.email as string) || "test" + Math.random() + "@test.com"
|
||||
const user = await prisma.user.create({
|
||||
data: {email, hashedPassword, role: "user"},
|
||||
select: {id: true, name: true, email: true, role: true},
|
||||
})
|
||||
|
||||
await blitzContext.session.$create({
|
||||
userId: user.id,
|
||||
})
|
||||
|
||||
res.status(200).json({userId: blitzContext.session.userId, ...user, email: req.query.email})
|
||||
})
|
||||
17
apps/web/pages/authenticated-page.tsx
Normal file
17
apps/web/pages/authenticated-page.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
const AuthPage = () => {
|
||||
return (
|
||||
<div>
|
||||
{JSON.stringify(
|
||||
{
|
||||
message: "This is a page that requires authentication",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
AuthPage.authenticate = true
|
||||
|
||||
export default AuthPage
|
||||
41
apps/web/pages/create-user.tsx
Normal file
41
apps/web/pages/create-user.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import {useState} from "react"
|
||||
import {SessionContext} from "@blitzjs/auth"
|
||||
import {useMutation} from "@blitzjs/rpc"
|
||||
import createUser from "app/mutations/createUser"
|
||||
import {User} from "prisma"
|
||||
|
||||
function Page() {
|
||||
const [name, setName] = useState("")
|
||||
const [email, setEmail] = useState("")
|
||||
const [createUserMutation, {error}] = useMutation(createUser)
|
||||
const [newUser, setNewUser] = useState<null | User>(null)
|
||||
|
||||
return (
|
||||
<div>
|
||||
New User Form:
|
||||
<form
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault()
|
||||
const user = await createUserMutation({name, email})
|
||||
setNewUser(user)
|
||||
}}
|
||||
>
|
||||
<label>
|
||||
Name:
|
||||
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
|
||||
</label>
|
||||
<label>
|
||||
Email:
|
||||
<input type="text" value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||
</label>
|
||||
<button type="submit">Create User</button>
|
||||
</form>
|
||||
<div style={{paddingTop: 20}}>
|
||||
<div>Error: {JSON.stringify(error, null, 2)}</div>
|
||||
New user: {JSON.stringify(newUser, null, 2)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Page
|
||||
15
apps/web/pages/index.tsx
Normal file
15
apps/web/pages/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import {invoke} from "@blitzjs/rpc"
|
||||
import getBasic from "app/queries/getBasic"
|
||||
|
||||
export const getServerSideProps = () => {
|
||||
return {props: {}}
|
||||
}
|
||||
|
||||
export default function Web() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Web</h1>
|
||||
<button onClick={() => invoke(getBasic, "FROM BROWSER")}>GetBasic</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
18
apps/web/pages/page-with-auth-redirect.tsx
Normal file
18
apps/web/pages/page-with-auth-redirect.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
const PageWithAuthRedirect = () => {
|
||||
return (
|
||||
<div>
|
||||
{JSON.stringify(
|
||||
{
|
||||
message:
|
||||
"This is a page that will redirect you to the Home page if you are authenticated",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
PageWithAuthRedirect.redirectAuthenticatedTo = "/"
|
||||
|
||||
export default PageWithAuthRedirect
|
||||
21
apps/web/pages/page-with-gsp.tsx
Normal file
21
apps/web/pages/page-with-gsp.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import {gSP} from "app/blitz-server"
|
||||
|
||||
export const getStaticProps = gSP(async ({ctx}) => {
|
||||
return {
|
||||
props: {
|
||||
data: {
|
||||
// userId: ctx?.session.userId,
|
||||
// session: {
|
||||
// id: session.userId,
|
||||
// publicData: session.$publicData,
|
||||
// },
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
function PageWithGsp({data}) {
|
||||
return <div>{JSON.stringify(data, null, 2)}</div>
|
||||
}
|
||||
|
||||
export default PageWithGsp
|
||||
23
apps/web/pages/page-with-gssp.tsx
Normal file
23
apps/web/pages/page-with-gssp.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import {SessionContext} from "@blitzjs/auth"
|
||||
import {gSSP} from "app/blitz-server"
|
||||
|
||||
type Props = {
|
||||
userId: unknown
|
||||
publicData: SessionContext["$publicData"]
|
||||
}
|
||||
|
||||
export const getServerSideProps = gSSP<Props>(async ({ctx}) => {
|
||||
const {session} = ctx
|
||||
return {
|
||||
props: {
|
||||
userId: session.userId,
|
||||
publicData: session.$publicData,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
function PageWithGssp(props: Props) {
|
||||
return <div>{JSON.stringify(props, null, 2)}</div>
|
||||
}
|
||||
|
||||
export default PageWithGssp
|
||||
18
apps/web/pages/page-with-redirect.tsx
Normal file
18
apps/web/pages/page-with-redirect.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
const PageWithRedirect = () => {
|
||||
return (
|
||||
<div>
|
||||
{JSON.stringify(
|
||||
{
|
||||
message:
|
||||
"This page requires authentication. It will redirect you to the Home page if you are NOT authenticated",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
PageWithRedirect.authenticate = {redirectTo: "/"}
|
||||
|
||||
export default PageWithRedirect
|
||||
6
apps/web/pages/page-with-use-auth-session.tsx
Normal file
6
apps/web/pages/page-with-use-auth-session.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import {useAuthenticatedSession} from "@blitzjs/auth"
|
||||
|
||||
export default function PageWithUseAuthSession() {
|
||||
useAuthenticatedSession()
|
||||
return <div>This page is using useAuthenticatedSession</div>
|
||||
}
|
||||
6
apps/web/pages/page-with-use-authorize-if.tsx
Normal file
6
apps/web/pages/page-with-use-authorize-if.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import {useAuthorizeIf} from "@blitzjs/auth"
|
||||
|
||||
export default function PageWithUseAuthorizeIf() {
|
||||
useAuthorizeIf(Math.random() > 0.5)
|
||||
return <div>This page is using useAuthorizeIf</div>
|
||||
}
|
||||
6
apps/web/pages/page-with-use-authorize.tsx
Normal file
6
apps/web/pages/page-with-use-authorize.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import {useAuthorize} from "@blitzjs/auth"
|
||||
|
||||
export default function PgaeWithUseAuthorize() {
|
||||
useAuthorize()
|
||||
return <div>This page is using useAuthorize</div>
|
||||
}
|
||||
6
apps/web/pages/page-with-use-redirect-auth.tsx
Normal file
6
apps/web/pages/page-with-use-redirect-auth.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import {useRedirectAuthenticated} from "@blitzjs/auth"
|
||||
|
||||
export default function PageWithUseRedirectAuth() {
|
||||
useRedirectAuthenticated("/")
|
||||
return <div>This page is using useRedirectAuthenticated</div>
|
||||
}
|
||||
6
apps/web/pages/page-with-use-session.tsx
Normal file
6
apps/web/pages/page-with-use-session.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import {useSession} from "@blitzjs/auth"
|
||||
|
||||
export default function PgaeWithUseSession() {
|
||||
const data = useSession()
|
||||
return <div>{JSON.stringify({data}, null, 2)}</div>
|
||||
}
|
||||
7
apps/web/pages/page-without-flicker.tsx
Normal file
7
apps/web/pages/page-without-flicker.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
const PageWithoutFlicker = ({data}) => {
|
||||
return <div>{JSON.stringify(data)}</div>
|
||||
}
|
||||
|
||||
PageWithoutFlicker.suppressFirstRenderFlicker = true
|
||||
|
||||
export default PageWithoutFlicker
|
||||
20
apps/web/pages/users.tsx
Normal file
20
apps/web/pages/users.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import {useQuery} from "@blitzjs/rpc"
|
||||
import getUsers from "app/queries/getUsers"
|
||||
|
||||
function UsersPage() {
|
||||
const [users] = useQuery(getUsers, {})
|
||||
return (
|
||||
<div>
|
||||
Users:
|
||||
<ul>
|
||||
{users.map((user) => (
|
||||
<li key={user.id}>
|
||||
{user.name} - {user.email}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UsersPage
|
||||
@@ -4,4 +4,5 @@ import {PrismaClient} from "@prisma/client"
|
||||
const EnhancedPrisma = enhancePrisma(PrismaClient)
|
||||
|
||||
export * from "@prisma/client"
|
||||
export default new EnhancedPrisma()
|
||||
const prisma = new EnhancedPrisma()
|
||||
export {prisma}
|
||||
@@ -6,7 +6,7 @@ CREATE TABLE "User" (
|
||||
"name" TEXT,
|
||||
"email" TEXT NOT NULL,
|
||||
"hashedPassword" TEXT,
|
||||
"role" TEXT NOT NULL DEFAULT 'USER'
|
||||
"role" TEXT NOT NULL DEFAULT 'user'
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
@@ -16,11 +16,11 @@ CREATE TABLE "Session" (
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"expiresAt" DATETIME,
|
||||
"handle" TEXT NOT NULL,
|
||||
"userId" INTEGER,
|
||||
"hashedSessionToken" TEXT,
|
||||
"antiCSRFToken" TEXT,
|
||||
"publicData" TEXT,
|
||||
"privateData" TEXT,
|
||||
"userId" INTEGER,
|
||||
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "sqlite"
|
||||
provider = "sqlite"
|
||||
@@ -1,24 +1,12 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
datasource sqlite {
|
||||
provider = "sqlite"
|
||||
url = "file:./db.sqlite"
|
||||
}
|
||||
|
||||
// SQLite is easy to start with, but if you use Postgres in production
|
||||
// you should also use it in development with the following:
|
||||
//datasource postgresql {
|
||||
// provider = "postgresql"
|
||||
// url = env("DATABASE_URL")
|
||||
//}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
@@ -60,11 +48,3 @@ model Token {
|
||||
|
||||
@@unique([hashedToken, type])
|
||||
}
|
||||
|
||||
model Project {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
name String
|
||||
dueDate DateTime?
|
||||
}
|
||||
7
apps/web/test/hello.unit.test.ts
Normal file
7
apps/web/test/hello.unit.test.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
function sum(a, b) {
|
||||
return a + b
|
||||
}
|
||||
|
||||
test("adds 1 + 2 to equal 3", () => {
|
||||
expect(sum(1, 2)).toBe(3)
|
||||
})
|
||||
8
apps/web/tsconfig.json
Normal file
8
apps/web/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@blitzjs/config/tsconfig.nextjs.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "jest.config.js"],
|
||||
"exclude": ["node_modules", "test"]
|
||||
}
|
||||
24
assets/digsas.svg
Normal file
24
assets/digsas.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M79.4831 1.95572C70.2813 -0.81923 55.2811 -0.617415 46.1549 2.4098L18.6125 11.5167C9.48627 14.5313 9.38542 19.7784 18.3856 23.1588L49.9743 35.028C58.9744 38.4084 73.6217 38.194 82.5084 34.5487L110.883 22.9192C119.782 19.2739 119.53 14.0267 110.316 11.2518L79.4831 1.95572Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M84.235 38.6101C75.3357 42.2554 68.0625 53.1029 68.0625 62.727V113.635C68.0625 123.259 74.9071 127.245 83.2644 122.489L109.282 107.706C117.639 102.951 124.912 91.208 125.442 81.6092L127.837 37.8281C128.366 28.2167 121.509 23.3479 112.609 26.9932L84.235 38.6101Z" fill="url(#paint1_linear)"/>
|
||||
<path d="M0.0071345 37.8409C-0.257575 28.2295 6.877 23.1211 15.8771 26.5015L47.4658 38.3707C56.466 41.7511 63.8274 52.3842 63.8274 62.0082V112.916C63.8274 122.54 56.882 126.715 48.386 122.212L17.0998 105.6C8.60392 101.085 1.44415 89.5306 1.16683 79.9192L0.0071345 37.8409Z" fill="url(#paint2_linear)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="63.9326" y1="0" x2="63.9326" y2="124.497" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#036EB8"/>
|
||||
<stop offset="1" stop-color="#469196"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="63.9326" y1="0" x2="63.9326" y2="124.497" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#036EB8"/>
|
||||
<stop offset="1" stop-color="#469196"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear" x1="63.9326" y1="0" x2="63.9326" y2="124.497" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#036EB8"/>
|
||||
<stop offset="1" stop-color="#469196"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0">
|
||||
<rect width="128" height="128" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/usertrack.png
Normal file
BIN
assets/usertrack.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
@@ -1,64 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
"@babel/preset-typescript",
|
||||
"@babel/preset-react",
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
modules: false,
|
||||
loose: true,
|
||||
exclude: [
|
||||
"@babel/plugin-transform-async-to-generator",
|
||||
"@babel/plugin-transform-regenerator",
|
||||
],
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
"babel-plugin-annotate-pure-calls",
|
||||
"babel-plugin-dev-expression",
|
||||
["@babel/plugin-proposal-class-properties", {loose: true}],
|
||||
"babel-plugin-macros",
|
||||
[
|
||||
"transform-inline-environment-variables",
|
||||
{
|
||||
include: ["BLITZ_PROD_BUILD"],
|
||||
},
|
||||
],
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
test: "./test/**/*",
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
modules: false,
|
||||
exclude: [
|
||||
"@babel/plugin-transform-async-to-generator",
|
||||
"@babel/plugin-transform-regenerator",
|
||||
],
|
||||
},
|
||||
],
|
||||
"blitz/babel",
|
||||
],
|
||||
plugins: [],
|
||||
},
|
||||
{
|
||||
test: "./nextjs/test/**/*",
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
modules: false,
|
||||
exclude: [
|
||||
"@babel/plugin-transform-async-to-generator",
|
||||
"@babel/plugin-transform-regenerator",
|
||||
],
|
||||
},
|
||||
],
|
||||
"blitz/babel",
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
extends: ["blitz"],
|
||||
}
|
||||
57
examples/auth/.gitignore
vendored
57
examples/auth/.gitignore
vendored
@@ -1,57 +0,0 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.pnp.*
|
||||
.npm
|
||||
web_modules/
|
||||
|
||||
# blitz
|
||||
/.blitz/
|
||||
/.next/
|
||||
*.sqlite
|
||||
.now
|
||||
.blitz-console-history
|
||||
blitz-log.log
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.envrc
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
*.lcov
|
||||
.nyc_output
|
||||
lib-cov
|
||||
|
||||
# Caches
|
||||
*.tsbuildinfo
|
||||
.eslintcache
|
||||
.node_repl_history
|
||||
.yarn-integrity
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
.vercel
|
||||
@@ -1 +0,0 @@
|
||||
save-exact=true
|
||||
@@ -1,5 +0,0 @@
|
||||
.gitkeep
|
||||
.env*
|
||||
*.ico
|
||||
*.lock
|
||||
db/migrations
|
||||
@@ -1,2 +0,0 @@
|
||||
.blitz
|
||||
*.sqlite
|
||||
@@ -1,26 +0,0 @@
|
||||
# auth
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Add this code to db/schema.prisma:
|
||||
|
||||
```
|
||||
model Project {
|
||||
id Int @default(autoincrement()) @id
|
||||
name String
|
||||
}
|
||||
```
|
||||
|
||||
2. DB migrate
|
||||
|
||||
```
|
||||
blitz prisma migrate dev
|
||||
```
|
||||
|
||||
3. Start the dev server
|
||||
|
||||
```
|
||||
blitz dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
@@ -1,145 +0,0 @@
|
||||
import {passportAuth, PublicData} from "blitz"
|
||||
import db from "db"
|
||||
import {Strategy as TwitterStrategy} from "passport-twitter"
|
||||
import {Strategy as GitHubStrategy} from "passport-github2"
|
||||
import {Strategy as Auth0Strategy} from "passport-auth0"
|
||||
import {Role} from "types"
|
||||
|
||||
function assert(condition: any, message: string): asserts condition {
|
||||
if (!condition) throw new Error(message)
|
||||
}
|
||||
|
||||
// These aren't required, but this is a good practice to ensure you have the env vars you need
|
||||
assert(process.env.TWITTER_CONSUMER_KEY, "You must provide the TWITTER_CONSUMER_KEY env variable")
|
||||
assert(
|
||||
process.env.TWITTER_CONSUMER_SECRET,
|
||||
"You must provide the TWITTER_CONSUMER_SECRET env variable",
|
||||
)
|
||||
assert(process.env.GITHUB_CLIENT_ID, "You must provide the GITHUB_CLIENT_ID env variable")
|
||||
assert(process.env.GITHUB_CLIENT_SECRET, "You must provide the GITHUB_CLIENT_SECRET env variable")
|
||||
|
||||
assert(process.env.AUTH0_DOMAIN, "You must provide the AUTH0_DOMAIN env variable")
|
||||
assert(process.env.AUTH0_CLIENT_ID, "You must provide the AUTH0_CLIENT_ID env variable")
|
||||
assert(process.env.AUTH0_CLIENT_SECRET, "You must provide the AUTH0_CLIENT_SECRET env variable")
|
||||
|
||||
export default passportAuth(({ctx, req, res}) => ({
|
||||
successRedirectUrl: "/",
|
||||
strategies: [
|
||||
{
|
||||
strategy: new TwitterStrategy(
|
||||
{
|
||||
consumerKey: process.env.TWITTER_CONSUMER_KEY as string,
|
||||
consumerSecret: process.env.TWITTER_CONSUMER_SECRET as string,
|
||||
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."))
|
||||
}
|
||||
|
||||
console.log(ctx.session.userId)
|
||||
|
||||
const user = await db.user.upsert({
|
||||
where: {email},
|
||||
create: {
|
||||
email,
|
||||
name: profile.displayName,
|
||||
},
|
||||
update: {email},
|
||||
})
|
||||
|
||||
const publicData = {userId: user.id, role: user.role, source: "twitter"}
|
||||
done(null, {publicData})
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
strategy: new GitHubStrategy(
|
||||
{
|
||||
clientID: process.env.GITHUB_CLIENT_ID as string,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
|
||||
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: string,
|
||||
_tokenSecret: string,
|
||||
profile: any,
|
||||
done: (err: Error | null, data?: {publicData: PublicData}) => void,
|
||||
) {
|
||||
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."))
|
||||
}
|
||||
|
||||
const user = await db.user.upsert({
|
||||
where: {email},
|
||||
create: {
|
||||
email,
|
||||
name: profile.displayName,
|
||||
},
|
||||
update: {email},
|
||||
})
|
||||
|
||||
const publicData = {
|
||||
userId: user.id,
|
||||
role: user.role as Role,
|
||||
source: "github",
|
||||
githubUsername: profile.username,
|
||||
}
|
||||
done(null, {publicData})
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
authenticateOptions: {scope: "openid email profile"},
|
||||
strategy: new Auth0Strategy(
|
||||
{
|
||||
domain: process.env.AUTH0_DOMAIN as string,
|
||||
clientID: process.env.AUTH0_CLIENT_ID as string,
|
||||
clientSecret: process.env.AUTH0_CLIENT_SECRET as string,
|
||||
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."))
|
||||
}
|
||||
|
||||
const user = await db.user.upsert({
|
||||
where: {email},
|
||||
create: {
|
||||
email,
|
||||
name: profile.displayName,
|
||||
},
|
||||
update: {email},
|
||||
})
|
||||
|
||||
const publicData = {
|
||||
userId: user.id,
|
||||
role: user.role,
|
||||
source: "auth0",
|
||||
githubUsername: profile.username,
|
||||
}
|
||||
done(undefined, {publicData})
|
||||
},
|
||||
),
|
||||
},
|
||||
],
|
||||
}))
|
||||
@@ -1,56 +0,0 @@
|
||||
import {AuthenticationError, Link, useMutation} from "blitz"
|
||||
import {LabeledTextField} from "app/core/components/LabeledTextField"
|
||||
import {Form, FORM_ERROR} from "app/core/components/Form"
|
||||
import login from "app/auth/mutations/login"
|
||||
import {Login} from "app/auth/validations"
|
||||
|
||||
import {Routes} from ".blitz"
|
||||
|
||||
type LoginFormProps = {
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
export const LoginForm = (props: LoginFormProps) => {
|
||||
const [loginMutation] = useMutation(login)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Login</h1>
|
||||
|
||||
<Form
|
||||
submitText="Login"
|
||||
schema={Login}
|
||||
initialValues={{email: "", password: ""}}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await loginMutation(values)
|
||||
props.onSuccess?.()
|
||||
} catch (error: any) {
|
||||
if (error instanceof 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" />
|
||||
<div>
|
||||
<Link href={Routes.ForgotPasswordPage()}>
|
||||
<a>Forgot your password?</a>
|
||||
</Link>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<div style={{marginTop: "1rem"}}>
|
||||
Or <Link href={Routes.SignupPage()}>Sign Up</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginForm
|
||||
@@ -1,44 +0,0 @@
|
||||
import React from "react"
|
||||
import {useMutation} from "blitz"
|
||||
import {LabeledTextField} from "app/core/components/LabeledTextField"
|
||||
import {Form, FORM_ERROR} from "app/core/components/Form"
|
||||
import signup from "app/auth/mutations/signup"
|
||||
import {Signup} 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={Signup}
|
||||
initialValues={{email: "", password: ""}}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await signupMutation(values)
|
||||
props.onSuccess?.()
|
||||
} catch (error: any) {
|
||||
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
|
||||
@@ -1,23 +0,0 @@
|
||||
import {NotFoundError, SecurePassword, resolver} from "blitz"
|
||||
import db from "db"
|
||||
import {authenticateUser} from "./login"
|
||||
import {ChangePassword} from "../validations"
|
||||
|
||||
export default resolver.pipe(
|
||||
resolver.zod(ChangePassword),
|
||||
resolver.authorize(),
|
||||
async ({currentPassword, newPassword}, ctx) => {
|
||||
const user = await db.user.findFirst({where: {id: ctx.session.userId!}})
|
||||
if (!user) throw new NotFoundError()
|
||||
|
||||
await authenticateUser(user.email, currentPassword)
|
||||
|
||||
const hashedPassword = await SecurePassword.hash(newPassword)
|
||||
await db.user.update({
|
||||
where: {id: user.id},
|
||||
data: {hashedPassword},
|
||||
})
|
||||
|
||||
return true
|
||||
},
|
||||
)
|
||||
@@ -1,56 +0,0 @@
|
||||
import {hash256, Ctx} from "blitz"
|
||||
import forgotPassword from "./forgotPassword"
|
||||
import db from "db"
|
||||
import previewEmail from "preview-email"
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.$reset()
|
||||
})
|
||||
|
||||
const generatedToken = "plain-token"
|
||||
jest.mock("next/stdlib-server", () => ({
|
||||
...jest.requireActual<object>("next/stdlib-server")!,
|
||||
generateToken: () => generatedToken,
|
||||
}))
|
||||
jest.mock("preview-email", () => jest.fn())
|
||||
|
||||
describe("forgotPassword mutation", () => {
|
||||
it("does not throw error if user doesn't exist", async () => {
|
||||
await expect(forgotPassword({email: "no-user@email.com"}, {} as Ctx)).resolves.not.toThrow()
|
||||
})
|
||||
|
||||
it("works correctly", async () => {
|
||||
// Create test user
|
||||
const user = await db.user.create({
|
||||
data: {
|
||||
email: "user@example.com",
|
||||
tokens: {
|
||||
// Create old token to ensure it's deleted
|
||||
create: {
|
||||
type: "RESET_PASSWORD",
|
||||
hashedToken: "token",
|
||||
expiresAt: new Date(),
|
||||
sentTo: "user@example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {tokens: true},
|
||||
})
|
||||
|
||||
// Invoke the mutation
|
||||
await forgotPassword({email: user.email}, {} as Ctx)
|
||||
|
||||
const tokens = await db.token.findMany({where: {userId: user.id}})
|
||||
const token = tokens[0]
|
||||
|
||||
// delete's existing tokens
|
||||
expect(tokens.length).toBe(1)
|
||||
|
||||
expect(token.id).not.toBe(user.tokens[0].id)
|
||||
expect(token.type).toBe("RESET_PASSWORD")
|
||||
expect(token.sentTo).toBe(user.email)
|
||||
expect(token.hashedToken).toBe(hash256(generatedToken))
|
||||
expect(token.expiresAt > new Date()).toBe(true)
|
||||
expect(previewEmail).toBeCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,41 +0,0 @@
|
||||
import {resolver, generateToken, hash256} from "blitz"
|
||||
import db from "db"
|
||||
import {forgotPasswordMailer} from "mailers/forgotPasswordMailer"
|
||||
import {ForgotPassword} from "../validations"
|
||||
|
||||
const RESET_PASSWORD_TOKEN_EXPIRATION_IN_HOURS = 4
|
||||
|
||||
export default resolver.pipe(resolver.zod(ForgotPassword), async ({email}) => {
|
||||
// 1. Get the user
|
||||
const user = await db.user.findFirst({where: {email: email.toLowerCase()}})
|
||||
|
||||
// 2. Generate the token and expiration date.
|
||||
const token = generateToken()
|
||||
const hashedToken = hash256(token)
|
||||
const expiresAt = new Date()
|
||||
expiresAt.setHours(expiresAt.getHours() + RESET_PASSWORD_TOKEN_EXPIRATION_IN_HOURS)
|
||||
|
||||
// 3. If user with this email was found
|
||||
if (user) {
|
||||
// 4. Delete any existing password reset tokens
|
||||
await db.token.deleteMany({where: {type: "RESET_PASSWORD", userId: user.id}})
|
||||
// 5. Save this new token in the database.
|
||||
await db.token.create({
|
||||
data: {
|
||||
user: {connect: {id: user.id}},
|
||||
type: "RESET_PASSWORD",
|
||||
expiresAt,
|
||||
hashedToken,
|
||||
sentTo: user.email,
|
||||
},
|
||||
})
|
||||
// 6. Send the email
|
||||
await forgotPasswordMailer({to: user.email, token}).send()
|
||||
} else {
|
||||
// 7. If no user found wait the same time so attackers can't tell the difference
|
||||
await new Promise((resolve) => setTimeout(resolve, 750))
|
||||
}
|
||||
|
||||
// 8. Return the same result whether a password reset email was sent or not
|
||||
return
|
||||
})
|
||||
@@ -1,37 +0,0 @@
|
||||
import {resolver, SecurePassword, AuthenticationError} from "blitz"
|
||||
import db from "db"
|
||||
import {Login} from "../validations"
|
||||
import {Role} from "types"
|
||||
|
||||
export const authenticateUser = async (email: string, password: string) => {
|
||||
const user = await db.user.findFirst({where: {email}})
|
||||
if (!user) throw new AuthenticationError()
|
||||
|
||||
const result = await SecurePassword.verify(user.hashedPassword, password)
|
||||
|
||||
if (result === SecurePassword.VALID_NEEDS_REHASH) {
|
||||
// Upgrade hashed password with a more secure hash
|
||||
const improvedHash = await SecurePassword.hash(password)
|
||||
await db.user.update({where: {id: user.id}, data: {hashedPassword: improvedHash}})
|
||||
}
|
||||
|
||||
const {hashedPassword, ...rest} = user
|
||||
return rest
|
||||
}
|
||||
|
||||
export default resolver.pipe(resolver.zod(Login), async ({email, password}, ctx) => {
|
||||
// This throws an error if credentials are invalid
|
||||
const user = await authenticateUser(email, password)
|
||||
|
||||
await ctx.session.$create({userId: user.id, role: user.role as Role})
|
||||
|
||||
return user
|
||||
})
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: {
|
||||
sizeLimit: "2mb",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import {Ctx} from "blitz"
|
||||
|
||||
export default async function logout(_: any, ctx: Ctx) {
|
||||
return await ctx.session.$revoke()
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
import resetPassword from "./resetPassword"
|
||||
import db from "db"
|
||||
import {hash256, SecurePassword} from "blitz"
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.$reset()
|
||||
})
|
||||
|
||||
const mockCtx: any = {
|
||||
session: {
|
||||
$create: jest.fn,
|
||||
},
|
||||
}
|
||||
|
||||
describe("resetPassword mutation", () => {
|
||||
it("works correctly", async () => {
|
||||
expect(true).toBe(true)
|
||||
|
||||
// Create test user
|
||||
const goodToken = "randomPasswordResetToken"
|
||||
const expiredToken = "expiredRandomPasswordResetToken"
|
||||
const future = new Date()
|
||||
future.setHours(future.getHours() + 4)
|
||||
const past = new Date()
|
||||
past.setHours(past.getHours() - 4)
|
||||
|
||||
const user = await db.user.create({
|
||||
data: {
|
||||
email: "user@example.com",
|
||||
tokens: {
|
||||
// Create old token to ensure it's deleted
|
||||
create: [
|
||||
{
|
||||
type: "RESET_PASSWORD",
|
||||
hashedToken: hash256(expiredToken),
|
||||
expiresAt: past,
|
||||
sentTo: "user@example.com",
|
||||
},
|
||||
{
|
||||
type: "RESET_PASSWORD",
|
||||
hashedToken: hash256(goodToken),
|
||||
expiresAt: future,
|
||||
sentTo: "user@example.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
include: {tokens: true},
|
||||
})
|
||||
|
||||
const newPassword = "newPassword"
|
||||
|
||||
// Non-existent token
|
||||
await expect(
|
||||
resetPassword({token: "no-token", password: "", passwordConfirmation: ""}, mockCtx),
|
||||
).rejects.toThrowError()
|
||||
|
||||
// Expired token
|
||||
await expect(
|
||||
resetPassword(
|
||||
{token: expiredToken, password: newPassword, passwordConfirmation: newPassword},
|
||||
mockCtx,
|
||||
),
|
||||
).rejects.toThrowError()
|
||||
|
||||
// Good token
|
||||
await resetPassword(
|
||||
{token: goodToken, password: newPassword, passwordConfirmation: newPassword},
|
||||
mockCtx,
|
||||
)
|
||||
|
||||
// Delete's the token
|
||||
const numberOfTokens = await db.token.count({where: {userId: user.id}})
|
||||
expect(numberOfTokens).toBe(0)
|
||||
|
||||
// Updates user's password
|
||||
const updatedUser = await db.user.findFirst({where: {id: user.id}})
|
||||
expect(await SecurePassword.verify(updatedUser!.hashedPassword, newPassword)).toBe(
|
||||
SecurePassword.VALID,
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,47 +0,0 @@
|
||||
import {resolver, SecurePassword, hash256} from "blitz"
|
||||
import db from "db"
|
||||
import {ResetPassword} from "../validations"
|
||||
import login from "./login"
|
||||
|
||||
export class ResetPasswordError extends Error {
|
||||
name = "ResetPasswordError"
|
||||
message = "Reset password link is invalid or it has expired."
|
||||
}
|
||||
|
||||
export default resolver.pipe(resolver.zod(ResetPassword), async ({password, token}, ctx) => {
|
||||
// 1. Try to find this token in the database
|
||||
const hashedToken = hash256(token)
|
||||
const possibleToken = await db.token.findFirst({
|
||||
where: {hashedToken, type: "RESET_PASSWORD"},
|
||||
include: {user: true},
|
||||
})
|
||||
|
||||
// 2. If token not found, error
|
||||
if (!possibleToken) {
|
||||
throw new ResetPasswordError()
|
||||
}
|
||||
const savedToken = possibleToken
|
||||
|
||||
// 3. Delete token so it can't be used again
|
||||
await db.token.delete({where: {id: savedToken.id}})
|
||||
|
||||
// 4. If token has expired, error
|
||||
if (savedToken.expiresAt < new Date()) {
|
||||
throw new ResetPasswordError()
|
||||
}
|
||||
|
||||
// 5. Since token is valid, now we can update the user's password
|
||||
const hashedPassword = await SecurePassword.hash(password)
|
||||
const user = await db.user.update({
|
||||
where: {id: savedToken.userId},
|
||||
data: {hashedPassword},
|
||||
})
|
||||
|
||||
// 6. Revoke all existing login sessions for this user
|
||||
await db.session.deleteMany({where: {userId: user.id}})
|
||||
|
||||
// 7. Now log the user in with the new credentials
|
||||
await login({email: user.email, password}, ctx)
|
||||
|
||||
return true
|
||||
})
|
||||
@@ -1,15 +0,0 @@
|
||||
import {resolver, SecurePassword} from "blitz"
|
||||
import db from "db"
|
||||
import {Signup} from "app/auth/validations"
|
||||
import {Role} from "types"
|
||||
|
||||
export default resolver.pipe(resolver.zod(Signup), async ({email, password}, ctx) => {
|
||||
const hashedPassword = await SecurePassword.hash(password)
|
||||
const user = await db.user.create({
|
||||
data: {email: email.toLowerCase(), hashedPassword, role: "user"},
|
||||
select: {id: true, name: true, email: true, role: true},
|
||||
})
|
||||
|
||||
await ctx.session.$create({userId: user.id, role: user.role as Role})
|
||||
return user
|
||||
})
|
||||
@@ -1,47 +0,0 @@
|
||||
import {BlitzPage, useMutation} from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import {LabeledTextField} from "app/core/components/LabeledTextField"
|
||||
import {Form, FORM_ERROR} from "app/core/components/Form"
|
||||
import {ForgotPassword} from "app/auth/validations"
|
||||
import forgotPassword from "app/auth/mutations/forgotPassword"
|
||||
|
||||
const ForgotPasswordPage: BlitzPage = () => {
|
||||
const [forgotPasswordMutation, {isSuccess}] = useMutation(forgotPassword)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Forgot your password?</h1>
|
||||
|
||||
{isSuccess ? (
|
||||
<div>
|
||||
<h2>Request Submitted</h2>
|
||||
<p>
|
||||
If your email is in our system, you will receive instructions to reset your password
|
||||
shortly.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<Form
|
||||
submitText="Send Reset Password Instructions"
|
||||
schema={ForgotPassword}
|
||||
initialValues={{email: ""}}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await forgotPasswordMutation(values)
|
||||
} catch (error) {
|
||||
return {
|
||||
[FORM_ERROR]: "Sorry, we had an unexpected error. Please try again.",
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LabeledTextField name="email" label="Email" placeholder="Email" />
|
||||
</Form>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ForgotPasswordPage.getLayout = (page) => <Layout title="Forgot Your Password?">{page}</Layout>
|
||||
|
||||
export default ForgotPasswordPage
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from "react"
|
||||
import {useRouter, BlitzPage} from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import {LoginForm} from "app/auth/components/LoginForm"
|
||||
import {Routes} from ".blitz"
|
||||
|
||||
const LoginPage: BlitzPage = () => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LoginForm
|
||||
onSuccess={() => {
|
||||
const next = router.query.next ? decodeURIComponent(router.query.next as string) : "/"
|
||||
router.push(next)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
LoginPage.redirectAuthenticatedTo = Routes.Home().pathname
|
||||
LoginPage.getLayout = (page) => <Layout title="Log In">{page}</Layout>
|
||||
|
||||
export default LoginPage
|
||||
@@ -1,60 +0,0 @@
|
||||
import {BlitzPage, useRouterQuery, Link, useMutation} from "blitz"
|
||||
import Layout from "app/core/layouts/Layout"
|
||||
import {LabeledTextField} from "app/core/components/LabeledTextField"
|
||||
import {Form, FORM_ERROR} from "app/core/components/Form"
|
||||
import {ResetPassword} from "app/auth/validations"
|
||||
import resetPassword from "app/auth/mutations/resetPassword"
|
||||
|
||||
import {Routes} from ".blitz"
|
||||
|
||||
const ResetPasswordPage: BlitzPage = () => {
|
||||
const query = useRouterQuery()
|
||||
const [resetPasswordMutation, {isSuccess}] = useMutation(resetPassword)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Set a New Password</h1>
|
||||
|
||||
{isSuccess ? (
|
||||
<div>
|
||||
<h2>Password Reset Successfully</h2>
|
||||
<p>
|
||||
Go to the <Link href={Routes.Home()}>homepage</Link>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<Form
|
||||
submitText="Reset Password"
|
||||
schema={ResetPassword}
|
||||
initialValues={{password: "", passwordConfirmation: "", token: query.token as string}}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await resetPasswordMutation(values)
|
||||
} catch (error: any) {
|
||||
if (error.name === "ResetPasswordError") {
|
||||
return {
|
||||
[FORM_ERROR]: error.message,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
[FORM_ERROR]: "Sorry, we had an unexpected error. Please try again.",
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LabeledTextField name="password" label="New Password" type="password" />
|
||||
<LabeledTextField
|
||||
name="passwordConfirmation"
|
||||
label="Confirm New Password"
|
||||
type="password"
|
||||
/>
|
||||
</Form>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ResetPasswordPage.getLayout = (page) => <Layout title="Reset Your Password">{page}</Layout>
|
||||
|
||||
export default ResetPasswordPage
|
||||
@@ -1,17 +0,0 @@
|
||||
import {useRouter, BlitzPage} from "blitz"
|
||||
import Layout from "app/core/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
|
||||
@@ -1,33 +0,0 @@
|
||||
import {z} from "zod"
|
||||
|
||||
const password = z.string().min(10).max(100)
|
||||
|
||||
export const Signup = z.object({
|
||||
email: z.string().email(),
|
||||
password,
|
||||
})
|
||||
|
||||
export const Login = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string(),
|
||||
})
|
||||
|
||||
export const ForgotPassword = z.object({
|
||||
email: z.string().email(),
|
||||
})
|
||||
|
||||
export const ResetPassword = z
|
||||
.object({
|
||||
password: password,
|
||||
passwordConfirmation: password,
|
||||
token: z.string(),
|
||||
})
|
||||
.refine((data) => data.password === data.passwordConfirmation, {
|
||||
message: "Passwords don't match",
|
||||
path: ["passwordConfirmation"], // set the path of the error
|
||||
})
|
||||
|
||||
export const ChangePassword = z.object({
|
||||
currentPassword: z.string(),
|
||||
newPassword: password,
|
||||
})
|
||||
@@ -1,59 +0,0 @@
|
||||
import React, {ReactNode, PropsWithoutRef} from "react"
|
||||
import {Form as FinalForm, FormProps as FinalFormProps} from "react-final-form"
|
||||
import {z} from "zod"
|
||||
import {validateZodSchema} from "blitz"
|
||||
export {FORM_ERROR} from "final-form"
|
||||
|
||||
export interface FormProps<S extends z.ZodType<any, any>>
|
||||
extends Omit<PropsWithoutRef<JSX.IntrinsicElements["form"]>, "onSubmit"> {
|
||||
/** 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"]
|
||||
}
|
||||
|
||||
export function Form<S extends z.ZodType<any, any>>({
|
||||
children,
|
||||
submitText,
|
||||
schema,
|
||||
initialValues,
|
||||
onSubmit,
|
||||
...props
|
||||
}: FormProps<S>) {
|
||||
return (
|
||||
<FinalForm
|
||||
initialValues={initialValues}
|
||||
validate={validateZodSchema(schema)}
|
||||
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>
|
||||
)}
|
||||
|
||||
{submitText && (
|
||||
<button type="submit" disabled={submitting}>
|
||||
{submitText}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<style global jsx>{`
|
||||
.form > * + * {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
`}</style>
|
||||
</form>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Form
|
||||
@@ -1,59 +0,0 @@
|
||||
import React, {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, {
|
||||
parse: props.type === "number" ? Number : undefined,
|
||||
})
|
||||
|
||||
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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user