Compare commits
20 Commits
release-20
...
@nhost/nho
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f33e07b191 | ||
|
|
017f1a6c7b | ||
|
|
93957c8af3 | ||
|
|
2505b2e26b | ||
|
|
5f4b4d2acc | ||
|
|
71a8ce4446 | ||
|
|
5ef5189898 | ||
|
|
791b7295fb | ||
|
|
25bc4b7fd6 | ||
|
|
da20159ec5 | ||
|
|
2ae5ea8bc1 | ||
|
|
3ba485e582 | ||
|
|
e5bab6a061 | ||
|
|
be64353145 | ||
|
|
2f5913c78d | ||
|
|
757ddd901c | ||
|
|
1a61c658a7 | ||
|
|
d3d14245c7 | ||
|
|
53d2f9d3e0 | ||
|
|
8c34c69e79 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@nhost/nextjs': minor
|
||||
---
|
||||
|
||||
chore: update peerDependency to support nextjs 14
|
||||
82
.github/workflows/gen_schedule_update_deps.yaml
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
name: "gen: update depenendencies"
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 1 * *'
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure aws
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_PRODUCTION_CORE_ACCOUNT_ID }}:role/github-actions-nhost-be
|
||||
aws-region: eu-central-1
|
||||
|
||||
- uses: nixbuild/nix-quick-install-action@v26
|
||||
with:
|
||||
nix_version: 2.16.2
|
||||
nix_conf: |
|
||||
experimental-features = nix-command flakes
|
||||
sandbox = false
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
substituters = https://cache.nixos.org/?priority=40 s3://nhost-nix-cache?region=eu-central-1&priority=50
|
||||
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ${{ secrets.NIX_CACHE_PUB_KEY }}
|
||||
|
||||
- name: Cache nix store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /nix
|
||||
key: nix-update-deps-${{ hashFiles('flakes.nix', 'flake.lock') }}
|
||||
|
||||
- name: Update nix flakes
|
||||
run: nix flake update
|
||||
|
||||
- name: Update dependencies
|
||||
run: |
|
||||
nix develop -c bash -c "
|
||||
pnpm dedupe
|
||||
pnpm update -r
|
||||
pnpm dedupe
|
||||
"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: Update dependencies
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: ${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>
|
||||
signoff: false
|
||||
branch: automated/update-deps
|
||||
delete-branch: true
|
||||
title: '[Scheduled] Update dependencies'
|
||||
body: |
|
||||
Dependencies updated
|
||||
|
||||
Note - If you see this PR and the checks haven't run, close and reopen the PR. See https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs
|
||||
labels: |
|
||||
dependencies
|
||||
draft: false
|
||||
|
||||
- name: "Cache nix store on s3"
|
||||
run: |
|
||||
echo ${{ secrets.NIX_CACHE_PRIV_KEY }} > cache-priv-key.pem
|
||||
nix build .\#devShells.x86_64-linux.default
|
||||
nix store sign --key-file cache-priv-key.pem --all
|
||||
nix copy --to s3://nhost-nix-cache\?region=eu-central-1 .\#devShells.x86_64-linux.default
|
||||
|
||||
- run: rm cache-priv-key.pem
|
||||
if: always()
|
||||
7
SECURITY.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
At Nhost, we take security vulnerabilities seriously and appreciate the assistance of the community in bringing any issues to our attention. If you discover a security vulnerability, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/nhost/nhost/security/advisories/new) tab.
|
||||
|
||||
Once you have submitted the report, we will promptly conduct a thorough investigation within 72 hours. In case we need further information, we may contact you for additional details. Rest assured that addressing the reported vulnerability in a timely manner is our top priority.
|
||||
@@ -1,5 +1,36 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 1.6.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@9.0.0
|
||||
- @nhost/nextjs@2.1.2
|
||||
|
||||
## 1.6.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@8.0.1
|
||||
- @nhost/nextjs@2.1.1
|
||||
|
||||
## 1.6.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5ef5189: fix: update `@apollo/client` to `3.9.4` to fix a cache bug
|
||||
|
||||
## 1.6.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3ba485e: fix: added discord.com to connect-src
|
||||
- e5bab6a: chore: update dependencies
|
||||
- Updated dependencies [b19ffed]
|
||||
- Updated dependencies [e5bab6a]
|
||||
- @nhost/nextjs@2.1.0
|
||||
- @nhost/react-apollo@8.0.0
|
||||
|
||||
## 1.6.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -4,10 +4,10 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
||||
});
|
||||
const { version } = require('./package.json');
|
||||
|
||||
|
||||
const cspHeader = `
|
||||
default-src 'self' *.nhost.run ws://*.nhost.run nhost.run ws://nhost.run;
|
||||
script-src 'self' 'unsafe-eval' 'unsafe-inline' cdn.segment.com js.stripe.com;
|
||||
connect-src 'self' *.nhost.run ws://*.nhost.run nhost.run ws://nhost.run discord.com;
|
||||
style-src 'self' 'unsafe-inline';
|
||||
img-src 'self' blob: data: avatars.githubusercontent.com s.gravatar.com *.nhost.run nhost.run;
|
||||
font-src 'self' data:;
|
||||
@@ -18,7 +18,7 @@ const cspHeader = `
|
||||
frame-src 'self' js.stripe.com;
|
||||
block-all-mixed-content;
|
||||
upgrade-insecure-requests;
|
||||
`
|
||||
`;
|
||||
|
||||
module.exports = withBundleAnalyzer({
|
||||
reactStrictMode: true,
|
||||
@@ -40,7 +40,7 @@ module.exports = withBundleAnalyzer({
|
||||
headers: [
|
||||
{
|
||||
key: 'X-Frame-Options',
|
||||
value: 'SAMEORIGIN'
|
||||
value: 'SAMEORIGIN',
|
||||
},
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
@@ -48,7 +48,7 @@ module.exports = withBundleAnalyzer({
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
];
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "1.6.5",
|
||||
"version": "1.6.9",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
@@ -19,7 +19,7 @@
|
||||
"e2e": "pnpm install-browsers && pnpm playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.8.9",
|
||||
"@apollo/client": "^3.9.4",
|
||||
"@codemirror/lang-sql": "^6.5.5",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
"@emotion/react": "^11.11.3",
|
||||
@@ -33,8 +33,8 @@
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@mui/base": "5.0.0-beta.31",
|
||||
"@mui/material": "^5.15.4",
|
||||
"@mui/system": "^5.15.4",
|
||||
"@mui/material": "^5.15.7",
|
||||
"@mui/system": "^5.15.7",
|
||||
"@mui/x-date-pickers": "^5.0.20",
|
||||
"@nhost/nextjs": "workspace:*",
|
||||
"@nhost/react-apollo": "workspace:*",
|
||||
@@ -43,24 +43,24 @@
|
||||
"@stripe/stripe-js": "^1.54.2",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@tanstack/react-table": "^8.11.6",
|
||||
"@tanstack/react-virtual": "^3.0.1",
|
||||
"@tanstack/react-table": "^8.11.7",
|
||||
"@tanstack/react-virtual": "^3.0.2",
|
||||
"@uiw/codemirror-theme-github": "^4.21.21",
|
||||
"@uiw/react-codemirror": "^4.21.21",
|
||||
"analytics-node": "^6.2.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"clsx": "^1.2.1",
|
||||
"date-fns": "^2.30.0",
|
||||
"framer-motion": "^10.18.0",
|
||||
"generate-password": "^1.7.1",
|
||||
"graphiql": "^3.1.0",
|
||||
"graphql": "16.8.1",
|
||||
"graphql-request": "^6.1.0",
|
||||
"framer-motion": "^10.17.9",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"graphql-ws": "^5.14.3",
|
||||
"just-kebab-case": "^4.2.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"next": "^14.0.4",
|
||||
"next": "^14.1.0",
|
||||
"next-seo": "^6.4.0",
|
||||
"node-pg-format": "^1.3.5",
|
||||
"pluralize": "^8.0.0",
|
||||
@@ -68,9 +68,9 @@
|
||||
"react-children-utilities": "^2.10.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-error-boundary": "^4.0.12",
|
||||
"react-hook-form": "^7.49.3",
|
||||
"react-hook-form": "^7.50.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-intersection-observer": "^9.5.3",
|
||||
"react-intersection-observer": "^9.5.4",
|
||||
"react-is": "18.2.0",
|
||||
"react-loading-skeleton": "^2.2.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
@@ -85,13 +85,13 @@
|
||||
"slugify": "^1.6.6",
|
||||
"stripe": "^10.17.0",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"utility-types": "^3.10.0",
|
||||
"utility-types": "^3.11.0",
|
||||
"validator": "^13.11.0",
|
||||
"yup": "^1.3.3",
|
||||
"yup-password": "^0.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.7",
|
||||
"@babel/core": "^7.23.9",
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@graphql-codegen/cli": "^3.3.1",
|
||||
"@graphql-codegen/typescript": "^3.0.4",
|
||||
@@ -106,34 +106,34 @@
|
||||
"@storybook/addon-postcss": "^2.0.0",
|
||||
"@storybook/builder-webpack5": "^6.5.16",
|
||||
"@storybook/manager-webpack5": "^6.5.16",
|
||||
"@storybook/react": "^6.5.16",
|
||||
"@storybook/react": "^7.6.15",
|
||||
"@storybook/testing-library": "^0.2.2",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^14.1.2",
|
||||
"@testing-library/react": "^14.2.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/ace": "^0.0.48",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/lodash.debounce": "^4.0.9",
|
||||
"@types/node": "^16.18.70",
|
||||
"@types/node": "^16.18.78",
|
||||
"@types/pluralize": "^0.0.30",
|
||||
"@types/react": "^18.2.47",
|
||||
"@types/react": "^18.2.50",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react-table": "^7.7.19",
|
||||
"@types/shell-quote": "^1.7.5",
|
||||
"@types/testing-library__jest-dom": "^5.14.9",
|
||||
"@types/validator": "^13.11.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.18.1",
|
||||
"@typescript-eslint/parser": "^6.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||
"@typescript-eslint/parser": "^6.20.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@vitest/coverage-v8": "^0.32.4",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"babel-loader": "^8.3.0",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"csstype": "^3.1.3",
|
||||
"dotenv": "^16.3.1",
|
||||
"dotenv": "^16.4.1",
|
||||
"encoding": "^0.1.13",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-airbnb": "19.0.4",
|
||||
@@ -145,7 +145,7 @@
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"jsdom": "^22.1.0",
|
||||
"lint-staged": "^15.2.0",
|
||||
"lint-staged": "^15.2.1",
|
||||
"msw": "^1.3.2",
|
||||
"msw-storybook-addon": "^1.10.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
@@ -161,7 +161,7 @@
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths-webpack-plugin": "^4.1.0",
|
||||
"vite": "^5.0.12",
|
||||
"vite-tsconfig-paths": "^4.2.3",
|
||||
"vite-tsconfig-paths": "^4.3.1",
|
||||
"vitest": "^0.32.4"
|
||||
},
|
||||
"browserslist": {
|
||||
|
||||
@@ -25,12 +25,12 @@ function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="my-4 grid w-full grid-flow-col items-center justify-between gap-2 px-1"
|
||||
className="grid items-center justify-between w-full grid-flow-col gap-2 px-1 my-4"
|
||||
onClick={setPlan}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<div className="grid grid-flow-row gap-y-0.5">
|
||||
<div className="grid grid-flow-col items-center justify-start gap-2">
|
||||
<div className="grid items-center justify-start grid-flow-col gap-2">
|
||||
<Checkbox
|
||||
onChange={setPlan}
|
||||
checked={selectedPlanId === planId}
|
||||
@@ -40,7 +40,7 @@ function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
|
||||
<Text
|
||||
variant="h3"
|
||||
component="p"
|
||||
className="self-center text-left font-medium"
|
||||
className="self-center font-medium text-left"
|
||||
>
|
||||
Upgrade to {planName}
|
||||
</Text>
|
||||
@@ -156,7 +156,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
||||
|
||||
if (pollingCurrentProject) {
|
||||
return (
|
||||
<Box className="mx-auto w-full max-w-xl rounded-lg p-6 text-left">
|
||||
<Box className="w-full max-w-xl p-6 mx-auto text-left rounded-lg">
|
||||
<div className="flex flex-col">
|
||||
<div className="mx-auto">
|
||||
<Image
|
||||
@@ -179,7 +179,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
className="mx-auto mt-4 w-full max-w-sm"
|
||||
className="w-full max-w-sm mx-auto mt-4"
|
||||
onClick={() => {
|
||||
if (close) {
|
||||
close();
|
||||
@@ -196,7 +196,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className="w-full max-w-xl rounded-lg p-6 text-left">
|
||||
<Box className="w-full max-w-xl p-6 text-left rounded-lg">
|
||||
<BaseDialog
|
||||
open={showPaymentModal}
|
||||
onClose={() => setShowPaymentModal(false)}
|
||||
@@ -241,7 +241,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-2 grid grid-flow-row gap-2">
|
||||
<div className="grid grid-flow-row gap-2 mt-2">
|
||||
<Button
|
||||
onClick={handleChangePlanClick}
|
||||
disabled={!selectedPlan}
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# @nhost/docs
|
||||
|
||||
## 2.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 791b729: fix: remove auth method
|
||||
|
||||
## 2.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- d3d1424: feat: Add support for authenticated download of files
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e5bab6a: chore: update dependencies
|
||||
- 2ae5ea8: fix: indicate that custom domains for postgres doesn't require configuration
|
||||
|
||||
## 2.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -14,14 +14,14 @@ If you are using the Nhost CLI for local development, as of [v0.12.0](https://gi
|
||||
<Step title="Start nhost">
|
||||
Run `nhost up`:
|
||||
|
||||

|
||||

|
||||
|
||||
After starting the service the first thing you will notice is that there is a new `ai` service running.
|
||||
</Step>
|
||||
<Step title="Commit metadata changes">
|
||||
As you start the AI service metadata changes may be proposed:
|
||||
|
||||

|
||||

|
||||
|
||||
We strongly recommmend you to commit them to your git repository so they can be deployed alongside your application.
|
||||
</Step>
|
||||
@@ -32,11 +32,11 @@ If you are using the Nhost CLI for local development, as of [v0.12.0](https://gi
|
||||
|
||||
If you add [auto-embeddings](/guides/ai/auto-embeddings) configuration locally and want to synchronize them with the cloud we recommend inserting them using a migration rather than with the auto-embeddings UI:
|
||||
|
||||

|
||||

|
||||
|
||||
And then running `nhost up` to download the updated metadata. Afterwards you should see both database migrations and functions' metadata changes in your local project:
|
||||
|
||||

|
||||

|
||||
|
||||
Pushing them to your deployment branch will also deploy them to your cloud project.
|
||||
|
||||
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 916 KiB After Width: | Height: | Size: 916 KiB |
|
Before Width: | Height: | Size: 393 KiB After Width: | Height: | Size: 393 KiB |
@@ -102,7 +102,7 @@
|
||||
"group": "AI",
|
||||
"pages": [
|
||||
"guides/ai/enabling-service",
|
||||
"guides/ai/local_development",
|
||||
"guides/ai/local-development",
|
||||
"guides/ai/auto-embeddings",
|
||||
"guides/ai/assistants",
|
||||
"guides/ai/dev-assistant"
|
||||
@@ -319,12 +319,13 @@
|
||||
"group": "Storage",
|
||||
"pages": [
|
||||
"reference/javascript/storage/hasura-storage-client",
|
||||
"reference/javascript/storage/delete",
|
||||
"reference/javascript/storage/upload",
|
||||
"reference/javascript/storage/download",
|
||||
"reference/javascript/storage/get-presigned-url",
|
||||
"reference/javascript/storage/get-public-url",
|
||||
"reference/javascript/storage/delete",
|
||||
"reference/javascript/storage/set-access-token",
|
||||
"reference/javascript/storage/set-admin-secret",
|
||||
"reference/javascript/storage/upload"
|
||||
"reference/javascript/storage/set-admin-secret"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@nhost/docs",
|
||||
"version": "2.2.0",
|
||||
"version": "2.4.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "mintlify dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mintlify": "^4.0.112"
|
||||
"mintlify": "^4.0.121"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ The following examples assume we are configuring custom domains at `*.custom-dom
|
||||
<Tab title="Config File">
|
||||
The first step is to add a CNAME record in your DNS provider for each of the services you want a custom domain for. You can find the instructions in the dashboard tab.
|
||||
|
||||
For Hasura, Auth, Functions, and PostgreSQL, custom domains are defined in the default `./nhost/config.toml` as follows:
|
||||
For Hasura, Auth, and Functions, custom domains are defined in the default `./nhost/config.toml` as follows:
|
||||
|
||||
```toml
|
||||
[[hasura.resources.networking.ingresses]]
|
||||
@@ -34,14 +34,15 @@ fqdn = ['hasura.custom-domain.com']
|
||||
[[auth.resources.networking.ingresses]]
|
||||
fqdn = ['auth.custom-domain.com']
|
||||
|
||||
[[postgres.resources.networking.ingresses]]
|
||||
fqdn = ['postgres.custom-domain.com']
|
||||
|
||||
[[functions.resources.networking.ingresses]]
|
||||
fqdn = ['functions.custom-domain.com']
|
||||
```
|
||||
|
||||
For Run services, typically in nhost-service.toml specific to the service:
|
||||
For PostgreSQL there is nothing to configure on Nhost's side so as long as you have a CNAME properly configured it should work.
|
||||
|
||||
<Note>To connect to your backend using your custom domains instead of the subdomain and region you will have to pass the verious service URLs to the client. You can find the relevant parameters to pass to the client in the reference documentation (i.e. [react](/reference/react/nhost-client#nhostclient))</Note>
|
||||
|
||||
For Run services, typically in an `nhost-service.toml` specific to the service:
|
||||
|
||||
```toml
|
||||
name = 'my-service'
|
||||
|
||||
@@ -9,10 +9,10 @@ Note: The Nhost client automatically refreshes the session when the user is auth
|
||||
|
||||
```ts
|
||||
// Refresh the session with the the current internal refresh token.
|
||||
nhost.auth.refreshToken()
|
||||
nhost.auth.refreshSession()
|
||||
|
||||
// Refresh the session with an external refresh token.
|
||||
nhost.auth.refreshToken(refreshToken)
|
||||
nhost.auth.refreshSession(refreshToken)
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: createFileUploadMachine()
|
||||
sidebarTitle: createFileUploadMachine()
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: createMultipleFilesUploadMachine()
|
||||
sidebarTitle: createMultipleFilesUploadMachine()
|
||||
---
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
title: uploadFilePromise()
|
||||
sidebarTitle: uploadFilePromise()
|
||||
---
|
||||
|
||||
## Parameters
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">params</span>** <span className="optional-status">required</span> <code>[`FileUploadConfig`](/reference/javascript/storage/types/file-upload-config) & Partial<StorageUploadFileParams></code>
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">interpreter</span>** <span className="optional-status">required</span> <code>ActorRefWithDeprecatedState<FileUploadContext, { type: "ADD", file: File, id: string, bucketId: string, name: string } | { type: "UPLOAD", file: File, id: string, name: string, bucketId: string } & FileUploadConfig | { type: "UPLOAD_PROGRESS", progress: number, loaded: number, additions: number } | { type: "UPLOAD_DONE", id: string, bucketId: string } | { type: "UPLOAD_ERROR", error: StorageErrorPayload } | { type: "CANCEL" } | { type: "DESTROY" }, { value: any, context: FileUploadContext }, ResolveTypegenMeta<Typegen0, { type: "ADD", file: File, id: string, bucketId: string, name: string } | { type: "UPLOAD", file: File, id: string, name: string, bucketId: string } & FileUploadConfig | { type: "UPLOAD_PROGRESS", progress: number, loaded: number, additions: number } | { type: "UPLOAD_DONE", id: string, bucketId: string } | { type: "UPLOAD_ERROR", error: StorageErrorPayload } | { type: "CANCEL" } | { type: "DESTROY" }, BaseActionObject, ServiceMap>> | Interpreter<FileUploadContext, any, { type: "ADD", file: File, id: string, bucketId: string, name: string } | { type: "UPLOAD", file: File, id: string, name: string, bucketId: string } & FileUploadConfig | { type: "UPLOAD_PROGRESS", progress: number, loaded: number, additions: number } | { type: "UPLOAD_DONE", id: string, bucketId: string } | { type: "UPLOAD_ERROR", error: StorageErrorPayload } | { type: "CANCEL" } | { type: "DESTROY" }, { value: any, context: FileUploadContext }, ResolveTypegenMeta<Typegen0, { type: "ADD", file: File, id: string, bucketId: string, name: string } | { type: "UPLOAD", file: File, id: string, name: string, bucketId: string } & FileUploadConfig | { type: "UPLOAD_PROGRESS", progress: number, loaded: number, additions: number } | { type: "UPLOAD_DONE", id: string, bucketId: string } | { type: "UPLOAD_ERROR", error: StorageErrorPayload } | { type: "CANCEL" } | { type: "DESTROY" }, BaseActionObject, ServiceMap>></code>
|
||||
|
||||
---
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
title: uploadMultipleFilesPromise()
|
||||
sidebarTitle: uploadMultipleFilesPromise()
|
||||
---
|
||||
|
||||
## Parameters
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">params</span>** <span className="optional-status">required</span> <code>[`FileUploadConfig`](/reference/javascript/storage/types/file-upload-config) & [`UploadMultipleFilesActionParams`](/reference/javascript/storage/types/upload-multiple-files-action-params)</code>
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">service</span>** <span className="optional-status">required</span> <code>Interpreter<MultipleFilesUploadContext, any, { type: "ADD", files: AnyFileList, bucketId: string } | { type: "UPLOAD", files: AnyFileList, bucketId: string } & FileUploadConfig | { type: "UPLOAD_PROGRESS", additions: number } | { type: "UPLOAD_DONE" } | { type: "UPLOAD_ERROR" } | { type: "CANCEL" } | { type: "REMOVE" } | { type: "CLEAR" }, { value: any, context: MultipleFilesUploadContext }, ResolveTypegenMeta<Typegen0, { type: "ADD", files: AnyFileList, bucketId: string } | { type: "UPLOAD", files: AnyFileList, bucketId: string } & FileUploadConfig | { type: "UPLOAD_PROGRESS", additions: number } | { type: "UPLOAD_DONE" } | { type: "UPLOAD_ERROR" } | { type: "CANCEL" } | { type: "REMOVE" } | { type: "CLEAR" }, BaseActionObject, ServiceMap>></code>
|
||||
|
||||
---
|
||||
27
docs/reference/javascript/storage/download.mdx
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: download()
|
||||
sidebarTitle: download()
|
||||
---
|
||||
|
||||
Use `nhost.storage.download` to download a file. To download a file the user must have permission to select the file in the `storage.files` table.
|
||||
|
||||
```ts
|
||||
const { file, error } = await nhost.storage.download({ fileId: '<File-ID>' })
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">params</span>** <span className="optional-status">required</span> [`StorageDownloadFileParams`](/reference/javascript/storage/types/storage-download-file-params)
|
||||
|
||||
| Property | Type | Required | Notes |
|
||||
| :----------------------------------------------------------------------------------------- | :---------------------------------------- | :------: | :----------------------------------------------------------- |
|
||||
| <span className="parameter-name"><span className="light-grey">params.</span>fileId</span> | <code>string</code> | ✔️ | |
|
||||
| <span className="parameter-name"><span className="light-grey">params.</span>blur</span> | <code>number</code> | | Image blur, between 0 and 100 |
|
||||
| <span className="parameter-name"><span className="light-grey">params.</span>quality</span> | <code>number</code> | | Image quality, between 1 and 100, 100 being the best quality |
|
||||
| <span className="parameter-name"><span className="light-grey">params.</span>height</span> | <code>number</code> | | Image height, in pixels |
|
||||
| <span className="parameter-name"><span className="light-grey">params.</span>width</span> | <code>number</code> | | Image width, in pixels |
|
||||
| <span className="parameter-name"><span className="light-grey">params.</span>headers</span> | <code>Record<string, string></code> | | Optional headers to be sent with the request |
|
||||
|
||||
---
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: StorageDownloadFileParams
|
||||
sidebarTitle: StorageDownloadFileParams
|
||||
description: No description provided.
|
||||
---
|
||||
|
||||
# `StorageDownloadFileParams`
|
||||
|
||||
## Parameters
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">fileId</span>** <span className="optional-status">required</span> <code>string</code>
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">blur</span>** <span className="optional-status">optional</span> <code>number</code>
|
||||
|
||||
Image blur, between 0 and 100
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">quality</span>** <span className="optional-status">optional</span> <code>number</code>
|
||||
|
||||
Image quality, between 1 and 100, 100 being the best quality
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">height</span>** <span className="optional-status">optional</span> <code>number</code>
|
||||
|
||||
Image height, in pixels
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">width</span>** <span className="optional-status">optional</span> <code>number</code>
|
||||
|
||||
Image width, in pixels
|
||||
|
||||
---
|
||||
|
||||
**<span className="parameter-name">headers</span>** <span className="optional-status">optional</span> <code>Record<string, string></code>
|
||||
|
||||
Optional headers to be sent with the request
|
||||
|
||||
---
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: StorageDownloadFileResponse
|
||||
sidebarTitle: StorageDownloadFileResponse
|
||||
description: No description provided.
|
||||
---
|
||||
|
||||
# `StorageDownloadFileResponse`
|
||||
|
||||
```ts
|
||||
type StorageDownloadFileResponse =
|
||||
| { file: Blob; error: null }
|
||||
| { file: null; error: Error }
|
||||
```
|
||||
@@ -11,18 +11,9 @@ info:
|
||||
servers:
|
||||
- url: https://local.auth.nhost.run/v1
|
||||
description: API Server
|
||||
security:
|
||||
- AdminSecret: []
|
||||
- BearerAuth: []
|
||||
|
||||
components:
|
||||
|
||||
securitySchemes:
|
||||
AdminSecret:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: X-Hasura-Admin-Secret
|
||||
description: Hasura Admin Secret
|
||||
BearerAuth:
|
||||
scheme: bearer
|
||||
type: http
|
||||
@@ -1524,7 +1515,6 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DisabledEndpointError'
|
||||
description: The feature is not activated
|
||||
security: []
|
||||
summary: Sign In TOTP
|
||||
tags:
|
||||
- Authentication
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# @nhost-examples/cli
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.5
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.4
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e5bab6a: chore: update dependencies
|
||||
- @nhost/nhost-js@3.0.3
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@nhost-examples/cli",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.6",
|
||||
"main": "src/index.mjs",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node src/index.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nhost/nhost-js": "^3.0.2",
|
||||
"@nhost/nhost-js": "workspace:^",
|
||||
"commander": "^10.0.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"dotenv": "^16.4.1",
|
||||
"graphql": "16.8.1",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"pino": "^8.17.2"
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# @nhost-examples/codegen-react-apollo
|
||||
|
||||
## 0.1.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [017f1a6]
|
||||
- @nhost/react@3.2.0
|
||||
- @nhost/react-apollo@9.0.0
|
||||
|
||||
## 0.1.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.1.1
|
||||
- @nhost/react-apollo@8.0.1
|
||||
|
||||
## 0.1.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e5bab6a: chore: update dependencies
|
||||
- Updated dependencies [1a61c65]
|
||||
- Updated dependencies [e5bab6a]
|
||||
- @nhost/react@3.1.0
|
||||
- @nhost/react-apollo@8.0.0
|
||||
|
||||
## 0.1.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-apollo",
|
||||
"version": "0.1.11",
|
||||
"version": "0.1.14",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen",
|
||||
@@ -15,9 +15,9 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.8.9",
|
||||
"@nhost/react": "^3.0.2",
|
||||
"@nhost/react-apollo": "^7.0.2",
|
||||
"@apollo/client": "^3.9.4",
|
||||
"@nhost/react": "workspace:^",
|
||||
"@nhost/react-apollo": "workspace:^",
|
||||
"clsx": "^1.2.1",
|
||||
"graphql": "16.8.1",
|
||||
"react": "^18.2.0",
|
||||
@@ -28,11 +28,11 @@
|
||||
"@graphql-codegen/client-preset": "^1.3.0",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@types/node": "^18.19.6",
|
||||
"@types/react": "^18.2.47",
|
||||
"@types/node": "^18.19.12",
|
||||
"@types/react": "^18.2.50",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.33",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^4.9.5",
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# @nhost-examples/codegen-react-query
|
||||
|
||||
## 0.1.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [017f1a6]
|
||||
- @nhost/react@3.2.0
|
||||
|
||||
## 0.1.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.1.1
|
||||
|
||||
## 0.1.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e5bab6a: chore: update dependencies
|
||||
- Updated dependencies [1a61c65]
|
||||
- Updated dependencies [e5bab6a]
|
||||
- @nhost/react@3.1.0
|
||||
|
||||
## 0.1.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-query",
|
||||
"version": "0.1.12",
|
||||
"version": "0.1.15",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen",
|
||||
@@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@nhost/react": "^3.0.2",
|
||||
"@nhost/react": "workspace:^",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@tanstack/react-query-devtools": "^4.36.1",
|
||||
"clsx": "^1.2.1",
|
||||
@@ -29,10 +29,10 @@
|
||||
"@graphql-codegen/client-preset": "^1.3.0",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@types/node": "^16.18.70",
|
||||
"@types/react": "^18.2.47",
|
||||
"@types/node": "^16.18.78",
|
||||
"@types/react": "^18.2.50",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"eslint": "^8.56.0",
|
||||
"postcss": "^8.4.33",
|
||||
"tailwindcss": "^3.4.1",
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# @nhost-examples/react-urql
|
||||
|
||||
## 0.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [017f1a6]
|
||||
- @nhost/react@3.2.0
|
||||
- @nhost/react-urql@6.0.0
|
||||
|
||||
## 0.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.1.1
|
||||
- @nhost/react-urql@5.0.1
|
||||
|
||||
## 0.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e5bab6a: chore: update dependencies
|
||||
- Updated dependencies [1a61c65]
|
||||
- Updated dependencies [e5bab6a]
|
||||
- @nhost/react@3.1.0
|
||||
- @nhost/react-urql@5.0.0
|
||||
|
||||
## 0.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-urql",
|
||||
"private": true,
|
||||
"version": "0.0.8",
|
||||
"version": "0.0.11",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
@@ -9,8 +9,8 @@
|
||||
"codegen": "graphql-codegen"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nhost/react": "^3.0.2",
|
||||
"@nhost/react-urql": "^4.0.2",
|
||||
"@nhost/react": "workspace:^",
|
||||
"@nhost/react-urql": "workspace:^",
|
||||
"clsx": "^1.2.1",
|
||||
"graphql": "16.8.1",
|
||||
"react": "18.2.0",
|
||||
@@ -22,11 +22,11 @@
|
||||
"@graphql-codegen/client-preset": "^1.3.0",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@types/node": "^16.18.70",
|
||||
"@types/react": "^18.2.47",
|
||||
"@types/node": "^16.18.78",
|
||||
"@types/react": "^18.2.50",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.33",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^4.9.5",
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# @nhost-examples/multi-tenant-one-to-many
|
||||
|
||||
## 2.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.5
|
||||
|
||||
## 2.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.4
|
||||
|
||||
## 2.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e5bab6a: chore: update dependencies
|
||||
- @nhost/nhost-js@3.0.3
|
||||
|
||||
## 2.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/multi-tenant-one-to-many",
|
||||
"private": true,
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.4",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
@@ -14,6 +14,6 @@
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nhost/nhost-js": "^3.0.2"
|
||||
"@nhost/nhost-js": "workspace:^"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,34 @@
|
||||
# @nhost-examples/nextjs
|
||||
|
||||
## 0.1.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [017f1a6]
|
||||
- @nhost/react@3.2.0
|
||||
- @nhost/react-apollo@9.0.0
|
||||
- @nhost/nextjs@2.1.2
|
||||
|
||||
## 0.1.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.1.1
|
||||
- @nhost/react-apollo@8.0.1
|
||||
- @nhost/nextjs@2.1.1
|
||||
|
||||
## 0.1.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e5bab6a: chore: update dependencies
|
||||
- Updated dependencies [1a61c65]
|
||||
- Updated dependencies [b19ffed]
|
||||
- Updated dependencies [e5bab6a]
|
||||
- @nhost/react@3.1.0
|
||||
- @nhost/nextjs@2.1.0
|
||||
- @nhost/react-apollo@8.0.0
|
||||
|
||||
## 0.1.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs",
|
||||
"version": "0.1.13",
|
||||
"version": "0.1.16",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -15,24 +15,24 @@
|
||||
"verify:fix": "run-p prettier:fix lint:fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.8.9",
|
||||
"@apollo/client": "^3.9.4",
|
||||
"@mantine/core": "^4.2.12",
|
||||
"@mantine/hooks": "^4.2.12",
|
||||
"@mantine/next": "^4.2.12",
|
||||
"@mantine/notifications": "^4.2.12",
|
||||
"@nhost/nextjs": "^2.0.2",
|
||||
"@nhost/react": "^3.0.2",
|
||||
"@nhost/react-apollo": "^7.0.2",
|
||||
"@nhost/nextjs": "workspace:^",
|
||||
"@nhost/react": "workspace:^",
|
||||
"@nhost/react-apollo": "workspace:^",
|
||||
"graphql": "16.8.1",
|
||||
"next": "^14.0.4",
|
||||
"next": "^14.1.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-icons": "^4.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "^12.3.4",
|
||||
"@types/node": "^16.18.70",
|
||||
"@types/react": "^18.2.47",
|
||||
"@types/node": "^16.18.78",
|
||||
"@types/react": "^18.2.50",
|
||||
"@xstate/inspect": "^0.6.5",
|
||||
"eslint-config-next": "12.0.10",
|
||||
"typescript": "^4.9.5",
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# @nhost-examples/node-storage
|
||||
|
||||
## 0.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.5
|
||||
|
||||
## 0.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.4
|
||||
|
||||
## 0.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e5bab6a: chore: update dependencies
|
||||
- @nhost/nhost-js@3.0.3
|
||||
|
||||
## 0.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/node-storage",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.8",
|
||||
"private": true,
|
||||
"description": "This is an example of how to use the Storage with Node.js",
|
||||
"main": "src/index.mjs",
|
||||
@@ -11,15 +11,15 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@nhost/nhost-js": "^3.0.2",
|
||||
"dotenv": "^16.3.1",
|
||||
"@nhost/nhost-js": "workspace:^",
|
||||
"dotenv": "^16.4.1",
|
||||
"form-data": "^4.0.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.19.6",
|
||||
"@types/uuid": "^9.0.7"
|
||||
"@types/node": "^18.19.12",
|
||||
"@types/uuid": "^9.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# @nhost-examples/nextjs-server-components
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.5
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.4
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- d3d1424: feat: Add support for authenticated download of files
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e5bab6a: chore: update dependencies
|
||||
- @nhost/nhost-js@3.0.3
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
serverActions: true
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs-server-components",
|
||||
"version": "0.1.5",
|
||||
"version": "0.2.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -9,7 +9,7 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.8.9",
|
||||
"@apollo/client": "^3.9.4",
|
||||
"@nhost/nhost-js": "workspace:^",
|
||||
"autoprefixer": "10.4.15",
|
||||
"cookies-next": "^3.0.0",
|
||||
@@ -18,10 +18,10 @@
|
||||
"form-data": "^4.0.0",
|
||||
"graphql": "16.8.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"next": "^14.0.4",
|
||||
"next": "^14.1.0",
|
||||
"postcss": "^8.4.33",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss": "3.3.3",
|
||||
"typescript": "5.2.2",
|
||||
@@ -30,7 +30,7 @@
|
||||
"devDependencies": {
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "20.5.6",
|
||||
"@types/react": "18.2.47",
|
||||
"@types/react-dom": "18.2.7"
|
||||
"@types/react": "^18.2.47",
|
||||
"@types/react-dom": "^18.2.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { DetailedHTMLProps, HTMLProps } from 'react'
|
||||
// @ts-ignore
|
||||
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
|
||||
import { useFormStatus } from 'react-dom'
|
||||
|
||||
export default function Input({
|
||||
id,
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import { ButtonHTMLAttributes, DetailedHTMLProps } from 'react'
|
||||
// @ts-ignore
|
||||
import { experimental_useFormStatus as useFormStatus } from 'react-dom'
|
||||
import { useFormStatus } from 'react-dom'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
type ButtonProps = {
|
||||
type?: 'button' | 'submit' | 'reset' | undefined;
|
||||
} & DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>;
|
||||
type?: 'button' | 'submit' | 'reset' | undefined
|
||||
} & DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
|
||||
|
||||
export default function SubmitButton({
|
||||
disabled,
|
||||
@@ -16,7 +16,7 @@ export default function SubmitButton({
|
||||
children,
|
||||
...rest
|
||||
}: ButtonProps) {
|
||||
const { pending } = useFormStatus();
|
||||
const { pending } = useFormStatus()
|
||||
|
||||
return (
|
||||
<button
|
||||
@@ -33,5 +33,5 @@ export default function SubmitButton({
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,20 @@ const TodoItem = ({ todo }: { todo: Todo }) => {
|
||||
await deleteTodo(todo.id)
|
||||
}
|
||||
|
||||
const handleDownloadAttachment = async () => {
|
||||
if (todo.attachment) {
|
||||
const response = await nhost.storage.download({ fileId: todo.attachment.id })
|
||||
if (response.file) {
|
||||
const url = window.URL.createObjectURL(response.file)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = todo.title
|
||||
a.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
@@ -74,6 +88,12 @@ const TodoItem = ({ todo }: { todo: Todo }) => {
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<button onClick={handleDownloadAttachment} className="w-6 h-6">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" strokeWidth={1.5} viewBox="0 0 512 512">
|
||||
<path d="M288 32c0-17.7-14.3-32-32-32s-32 14.3-32 32V274.7l-73.4-73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0l128-128c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L288 274.7V32zM64 352c-35.3 0-64 28.7-64 64v32c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V416c0-35.3-28.7-64-64-64H346.5l-45.3 45.3c-25 25-65.5 25-90.5 0L165.5 352H64zm368 56a24 24 0 1 1 0 48 24 24 0 1 1 0-48z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button onClick={handleDeleteTodo} className="w-6 h-6">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -54,16 +54,17 @@ insert_permissions:
|
||||
permission:
|
||||
check: {}
|
||||
columns:
|
||||
- is_uploaded
|
||||
- size
|
||||
- bucket_id
|
||||
- etag
|
||||
- mime_type
|
||||
- name
|
||||
- id
|
||||
- created_at
|
||||
- updated_at
|
||||
- id
|
||||
- bucket_id
|
||||
- name
|
||||
- size
|
||||
- mime_type
|
||||
- etag
|
||||
- is_uploaded
|
||||
- uploaded_by_user_id
|
||||
- metadata
|
||||
- role: user
|
||||
permission:
|
||||
check: {}
|
||||
@@ -78,16 +79,17 @@ select_permissions:
|
||||
- role: public
|
||||
permission:
|
||||
columns:
|
||||
- is_uploaded
|
||||
- size
|
||||
- bucket_id
|
||||
- etag
|
||||
- mime_type
|
||||
- name
|
||||
- id
|
||||
- created_at
|
||||
- updated_at
|
||||
- id
|
||||
- bucket_id
|
||||
- name
|
||||
- size
|
||||
- mime_type
|
||||
- etag
|
||||
- is_uploaded
|
||||
- uploaded_by_user_id
|
||||
- metadata
|
||||
filter: {}
|
||||
- role: user
|
||||
permission:
|
||||
|
||||
@@ -146,7 +146,7 @@ version = '14.6-20230406-2'
|
||||
[provider]
|
||||
|
||||
[storage]
|
||||
version = '0.4.0'
|
||||
version = '0.5.1'
|
||||
|
||||
[observability]
|
||||
[observability.grafana]
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
---
|
||||
|
||||
## 0.2.3
|
||||
### Patch Changes
|
||||
|
||||
- e5bab6a: chore: update dependencies
|
||||
|
||||
## 0.2.2
|
||||
### Patch Changes
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/sveltekit",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -16,11 +16,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nhost/nhost-js": "2.2.18",
|
||||
"@playwright/test": "^1.41.0",
|
||||
"@playwright/test": "^1.41.1",
|
||||
"@sveltejs/adapter-auto": "^2.1.1",
|
||||
"@sveltejs/kit": "^1.30.3",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
|
||||
@@ -1,5 +1,38 @@
|
||||
# @nhost-examples/react-apollo
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 017f1a6: feat: add elevated permission examples
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [017f1a6]
|
||||
- @nhost/react@3.2.0
|
||||
- @nhost/react-apollo@9.0.0
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.1.1
|
||||
- @nhost/react-apollo@8.0.1
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 1a61c65: feat: add 'elevateEmailSecurityKey' to the SDKs along with integration into react-apollo and vue-apollo examples
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e5bab6a: chore: update dependencies
|
||||
- Updated dependencies [1a61c65]
|
||||
- Updated dependencies [e5bab6a]
|
||||
- @nhost/react@3.1.0
|
||||
- @nhost/react-apollo@8.0.0
|
||||
|
||||
## 0.1.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
schema:
|
||||
- http://local.graphql.nhost.run/v1:
|
||||
- https://local.hasura.nhost.run/v1/graphql:
|
||||
headers:
|
||||
x-hasura-admin-secret: nhost-admin-secret
|
||||
x-hasura-role: user
|
||||
@@ -15,7 +15,6 @@ generates:
|
||||
bigint: number
|
||||
citext: string
|
||||
timestamptz: string
|
||||
|
||||
plugins:
|
||||
- typescript
|
||||
- typescript-operations
|
||||
|
||||
@@ -46,4 +46,4 @@ delete_permissions:
|
||||
permission:
|
||||
filter:
|
||||
user_id:
|
||||
_eq: X-Hasura-User-Id
|
||||
_eq: x-hasura-auth-elevated
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
table:
|
||||
name: notes
|
||||
schema: public
|
||||
configuration:
|
||||
column_config:
|
||||
created_at:
|
||||
custom_name: createdAt
|
||||
updated_at:
|
||||
custom_name: updatedAt
|
||||
custom_column_names:
|
||||
created_at: createdAt
|
||||
updated_at: updatedAt
|
||||
custom_root_fields:
|
||||
delete: deleteNotes
|
||||
delete_by_pk: deleteNote
|
||||
insert: inserNotes
|
||||
insert_one: insertNote
|
||||
select_aggregate: notesAggregate
|
||||
select_by_pk: note
|
||||
update: updateNotes
|
||||
update_by_pk: updateNote
|
||||
object_relationships:
|
||||
- name: user
|
||||
using:
|
||||
foreign_key_constraint_on: user_id
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
check:
|
||||
user_id:
|
||||
_eq: x-hasura-auth-elevated
|
||||
set:
|
||||
user_id: x-hasura-User-Id
|
||||
columns:
|
||||
- content
|
||||
select_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- content
|
||||
- created_at
|
||||
- id
|
||||
- updated_at
|
||||
filter:
|
||||
user_id:
|
||||
_eq: X-Hasura-User-Id
|
||||
allow_aggregations: true
|
||||
update_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- content
|
||||
filter:
|
||||
user_id:
|
||||
_eq: x-hasura-auth-elevated
|
||||
check: null
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
filter:
|
||||
user_id:
|
||||
_eq: x-hasura-auth-elevated
|
||||
@@ -0,0 +1,42 @@
|
||||
table:
|
||||
name: virus
|
||||
schema: storage
|
||||
configuration:
|
||||
column_config:
|
||||
created_at:
|
||||
custom_name: createdAt
|
||||
file_id:
|
||||
custom_name: fileId
|
||||
filename:
|
||||
custom_name: filename
|
||||
id:
|
||||
custom_name: id
|
||||
updated_at:
|
||||
custom_name: updatedAt
|
||||
user_session:
|
||||
custom_name: userSession
|
||||
virus:
|
||||
custom_name: virus
|
||||
custom_column_names:
|
||||
created_at: createdAt
|
||||
file_id: fileId
|
||||
filename: filename
|
||||
id: id
|
||||
updated_at: updatedAt
|
||||
user_session: userSession
|
||||
virus: virus
|
||||
custom_name: virus
|
||||
custom_root_fields:
|
||||
delete: deleteViruses
|
||||
delete_by_pk: deleteVirus
|
||||
insert: insertViruses
|
||||
insert_one: insertVirus
|
||||
select: viruses
|
||||
select_aggregate: virusesAggregate
|
||||
select_by_pk: virus
|
||||
update: updateViruses
|
||||
update_by_pk: updateVirus
|
||||
object_relationships:
|
||||
- name: file
|
||||
using:
|
||||
foreign_key_constraint_on: file_id
|
||||
@@ -7,6 +7,8 @@
|
||||
- "!include auth_user_roles.yaml"
|
||||
- "!include auth_user_security_keys.yaml"
|
||||
- "!include auth_users.yaml"
|
||||
- "!include public_notes.yaml"
|
||||
- "!include public_todos.yaml"
|
||||
- "!include storage_buckets.yaml"
|
||||
- "!include storage_files.yaml"
|
||||
- "!include storage_virus.yaml"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE "public"."notes";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE TABLE "public"."notes" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "content" text NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "user_id" uuid NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"));
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
@@ -1,7 +1,7 @@
|
||||
[global]
|
||||
|
||||
[hasura]
|
||||
version = 'v2.25.1-ce'
|
||||
version = 'v2.33.4-ce'
|
||||
adminSecret = '{{ secrets.HASURA_GRAPHQL_ADMIN_SECRET }}'
|
||||
webhookSecret = '{{ secrets.NHOST_WEBHOOK_SECRET }}'
|
||||
|
||||
@@ -28,11 +28,14 @@ httpPoolSize = 100
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.22.1'
|
||||
version = '0.26.0'
|
||||
|
||||
[auth.elevatedPrivileges]
|
||||
mode = 'required'
|
||||
|
||||
[auth.redirections]
|
||||
clientUrl = 'https://react-apollo.example.nhost.io/'
|
||||
allowedUrls = ['https://react-apollo.example.nhost.io/profile', 'http://localhost:30000']
|
||||
allowedUrls = ['https://react-apollo.example.nhost.io/profile', 'http://localhost:3000']
|
||||
|
||||
[auth.signUp]
|
||||
enabled = true
|
||||
@@ -149,12 +152,12 @@ enabled = true
|
||||
issuer = 'nhost'
|
||||
|
||||
[postgres]
|
||||
version = '14.6-20230406-2'
|
||||
version = '14.6-20240129-1'
|
||||
|
||||
[provider]
|
||||
|
||||
[storage]
|
||||
version = '0.3.4'
|
||||
version = '0.6.0'
|
||||
|
||||
[observability]
|
||||
[observability.grafana]
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-apollo",
|
||||
"version": "0.1.18",
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.8.9",
|
||||
"@apollo/client": "^3.9.4",
|
||||
"@mantine/core": "^4.2.12",
|
||||
"@mantine/dropzone": "^4.2.12",
|
||||
"@mantine/hooks": "^4.2.12",
|
||||
"@mantine/notifications": "^4.2.12",
|
||||
"@mantine/prism": "^4.2.12",
|
||||
"@nhost/react": "^3.0.2",
|
||||
"@nhost/react-apollo": "^7.0.2",
|
||||
"@nhost/react": "workspace:^",
|
||||
"@nhost/react-apollo": "workspace:^",
|
||||
"graphql": "16.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.12.0",
|
||||
"react-router": "^6.21.2",
|
||||
"react-router-dom": "^6.21.2",
|
||||
"react-router": "^6.21.3",
|
||||
"react-router-dom": "^6.21.3",
|
||||
"tabler-icons-react": "^1.56.0"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -54,12 +54,12 @@
|
||||
"@nuintun/qrcode": "^3.4.0",
|
||||
"@playwright/test": "1.31.0",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react": "^18.2.47",
|
||||
"@types/react": "^18.2.50",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/totp-generator": "^0.0.4",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"@xstate/inspect": "^0.6.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"dotenv": "^16.4.1",
|
||||
"jsqr": "^1.4.0",
|
||||
"pngjs": "^7.0.0",
|
||||
"totp-generator": "^0.0.13",
|
||||
|
||||
2
examples/react-apollo/pnpm-lock.yaml
generated
@@ -2056,7 +2056,7 @@ packages:
|
||||
normalize-path: 3.0.0
|
||||
readdirp: 3.6.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/clean-stack@2.2.0:
|
||||
|
||||
@@ -15,6 +15,7 @@ import { SignUpPage } from './sign-up'
|
||||
import { StoragePage } from './Storage'
|
||||
|
||||
import './App.css?inline'
|
||||
import { NotesPage } from './components/notes'
|
||||
const title = 'Nhost with React and Apollo'
|
||||
|
||||
function App() {
|
||||
@@ -94,6 +95,16 @@ function App() {
|
||||
</AuthGate>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/secret-notes"
|
||||
element={
|
||||
<AuthGate>
|
||||
<NotesPage />
|
||||
</AuthGate>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/storage"
|
||||
element={
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import { FaFile, FaHouseUser, FaQuestion, FaSignOutAlt } from 'react-icons/fa'
|
||||
import { FaFile, FaHouseUser, FaQuestion, FaSignOutAlt, FaLock } from 'react-icons/fa'
|
||||
import { SiApollographql } from 'react-icons/si'
|
||||
import { useLocation, useNavigate } from 'react-router'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { showNotification } from '@mantine/notifications'
|
||||
|
||||
import { Group, MantineColor, Navbar, Text, ThemeIcon, UnstyledButton } from '@mantine/core'
|
||||
import { useAuthenticated, useSignOut } from '@nhost/react'
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Group,
|
||||
MantineColor,
|
||||
Navbar,
|
||||
Text,
|
||||
ThemeIcon,
|
||||
UnstyledButton
|
||||
} from '@mantine/core'
|
||||
import { useAuthenticated, useElevateSecurityKeyEmail, useSignOut, useUserData } from '@nhost/react'
|
||||
interface MenuItemProps {
|
||||
icon: React.ReactNode
|
||||
color?: MantineColor
|
||||
@@ -52,16 +62,52 @@ const MenuItem: React.FC<MenuItemProps> = ({ icon, color, label, link, action })
|
||||
const data: MenuItemProps[] = [
|
||||
{ icon: <FaHouseUser size={16} />, label: 'Home', link: '/' },
|
||||
{ icon: <FaHouseUser size={16} />, label: 'Profile', link: '/profile' },
|
||||
{ icon: <FaLock size={16} />, label: 'Secret Notes', link: '/secret-notes' },
|
||||
{ icon: <FaFile size={16} />, label: 'Storage', link: '/storage' },
|
||||
{ icon: <SiApollographql size={16} />, label: 'Apollo', link: '/apollo' },
|
||||
{ icon: <FaQuestion size={16} />, label: 'About', link: '/about' }
|
||||
]
|
||||
|
||||
export default function NavBar() {
|
||||
const authenticated = useAuthenticated()
|
||||
const { signOut } = useSignOut()
|
||||
const userData = useUserData()
|
||||
const navigate = useNavigate()
|
||||
const { signOut } = useSignOut()
|
||||
const authenticated = useAuthenticated()
|
||||
const { elevateEmailSecurityKey, elevated } = useElevateSecurityKeyEmail()
|
||||
|
||||
const handleElevate = async () => {
|
||||
if (!authenticated) {
|
||||
showNotification({
|
||||
color: 'red',
|
||||
title: 'Logged out',
|
||||
message: 'Please login first'
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (userData?.email) {
|
||||
const { elevated, isError } = await elevateEmailSecurityKey(userData.email)
|
||||
|
||||
if (elevated) {
|
||||
showNotification({
|
||||
title: 'Success',
|
||||
message: 'You now have an elevated permission'
|
||||
})
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
showNotification({
|
||||
color: 'red',
|
||||
title: 'Failed',
|
||||
message: 'Could not elevate permission'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const links = data.map((link) => <MenuItem {...link} key={link.label} />)
|
||||
|
||||
return (
|
||||
<Navbar width={{ sm: 300, lg: 400, base: 100 }} aria-label="main navigation">
|
||||
<Navbar.Section grow mt="md">
|
||||
@@ -77,6 +123,13 @@ export default function NavBar() {
|
||||
/>
|
||||
)}
|
||||
</Navbar.Section>
|
||||
|
||||
<Card p="lg" m="sm">
|
||||
<Group position="apart">
|
||||
<span>Elevated permissions: {String(elevated)}</span>
|
||||
<Button onClick={handleElevate}>Elevate</Button>
|
||||
</Group>
|
||||
</Card>
|
||||
</Navbar>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ const LoadingComponent: React.FC<React.PropsWithChildren<{ connectionAttempts: n
|
||||
|
||||
export const AuthGate: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||
const { isLoading, isAuthenticated, connectionAttempts } = useAuthenticationStatus()
|
||||
|
||||
const location = useLocation()
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingComponent connectionAttempts={connectionAttempts} />
|
||||
}
|
||||
|
||||
220
examples/react-apollo/src/components/notes/index.tsx
Normal file
@@ -0,0 +1,220 @@
|
||||
import {
|
||||
DeleteNoteMutation,
|
||||
InsertNoteMutation,
|
||||
NotesListQuery,
|
||||
SecurityKeysQuery
|
||||
} from 'src/generated'
|
||||
|
||||
import { gql, useMutation } from '@apollo/client'
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Container,
|
||||
Grid,
|
||||
Loader,
|
||||
TextInput,
|
||||
Title,
|
||||
Group,
|
||||
ActionIcon
|
||||
} from '@mantine/core'
|
||||
import { useInputState } from '@mantine/hooks'
|
||||
import { showNotification } from '@mantine/notifications'
|
||||
import { useAuthQuery } from '@nhost/react-apollo'
|
||||
import { useElevateSecurityKeyEmail, useUserData } from '@nhost/react'
|
||||
import { FaTrash } from 'react-icons/fa'
|
||||
import { SECURITY_KEYS_LIST } from 'src/utils'
|
||||
import { useState } from 'react'
|
||||
|
||||
const NOTES_LIST = gql`
|
||||
query notesList {
|
||||
notes {
|
||||
id
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const INSERT_NOTE = gql`
|
||||
mutation insertNote($content: String!) {
|
||||
insertNote(object: { content: $content }) {
|
||||
id
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const DELETE_NOTE = gql`
|
||||
mutation deleteNote($noteId: uuid!) {
|
||||
deleteNote(id: $noteId) {
|
||||
id
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const NotesPage: React.FC = () => {
|
||||
const userData = useUserData()
|
||||
|
||||
const { loading, data } = useAuthQuery<NotesListQuery>(NOTES_LIST, {
|
||||
pollInterval: 5000,
|
||||
fetchPolicy: 'cache-and-network'
|
||||
})
|
||||
|
||||
const [content, setContent] = useInputState('')
|
||||
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
|
||||
|
||||
const [userHasSecurityKey, setUserHasSecurityKey] = useState(false)
|
||||
|
||||
useAuthQuery<SecurityKeysQuery>(SECURITY_KEYS_LIST, {
|
||||
variables: { userId: userData?.id },
|
||||
onCompleted: ({ authUserSecurityKeys }) => {
|
||||
setUserHasSecurityKey(authUserSecurityKeys?.length > 0)
|
||||
}
|
||||
})
|
||||
|
||||
const [addNoteMutation] = useMutation<InsertNoteMutation>(INSERT_NOTE)
|
||||
const [deleteNoteMutation] = useMutation<DeleteNoteMutation>(DELETE_NOTE)
|
||||
|
||||
const checkElevatedPermission = async () => {
|
||||
if (!elevated && userHasSecurityKey) {
|
||||
const { elevated } = await elevateEmailSecurityKey(userData?.email as string)
|
||||
|
||||
if (!elevated) {
|
||||
throw new Error('Permissions were not elevated')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const add = async () => {
|
||||
if (!content) return
|
||||
|
||||
try {
|
||||
await checkElevatedPermission()
|
||||
} catch (error) {
|
||||
showNotification({
|
||||
title: 'Error',
|
||||
message: 'Could not elevate permissions'
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
addNoteMutation({
|
||||
variables: { content },
|
||||
onCompleted: () => setContent(''),
|
||||
onError: (error) => {
|
||||
showNotification({
|
||||
color: 'red',
|
||||
title: error.networkError ? 'Network error' : 'Error',
|
||||
message: error.message
|
||||
})
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
cache.modify({
|
||||
fields: {
|
||||
notes(existingNotes = []) {
|
||||
const newNoteRef = cache.writeFragment({
|
||||
data: data?.insertNote,
|
||||
fragment: gql`
|
||||
fragment NewNote on notes {
|
||||
id
|
||||
content
|
||||
}
|
||||
`
|
||||
})
|
||||
return [...existingNotes, newNoteRef]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const deleteNote = async (noteId: string) => {
|
||||
if (!noteId) return
|
||||
|
||||
try {
|
||||
await checkElevatedPermission()
|
||||
} catch (error) {
|
||||
showNotification({
|
||||
title: 'Error',
|
||||
message: 'Could not elevate permissions'
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
deleteNoteMutation({
|
||||
variables: { noteId },
|
||||
onCompleted: () => setContent(''),
|
||||
onError: (error) => {
|
||||
showNotification({
|
||||
color: 'red',
|
||||
title: error.networkError ? 'Network error' : 'Error',
|
||||
message: error.message
|
||||
})
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
const deletedNoteId = data?.deleteNote?.id
|
||||
if (deletedNoteId) {
|
||||
cache.modify({
|
||||
fields: {
|
||||
notes(existingNotes = [], { readField }) {
|
||||
// @ts-ignore
|
||||
return existingNotes.filter((noteRef) => noteId !== readField('id', noteRef))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{loading && <Loader />}
|
||||
<Card shadow="sm" p="lg" m="sm">
|
||||
<Title>Secret Notes</Title>
|
||||
<Grid>
|
||||
<Grid.Col span={10}>
|
||||
<TextInput
|
||||
value={content}
|
||||
onChange={setContent}
|
||||
autoFocus
|
||||
onKeyDown={(e) => e.code === 'Enter' && add()}
|
||||
/>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={2}>
|
||||
<Button
|
||||
fullWidth
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
add()
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
<ul style={{ paddingLeft: 12 }}>
|
||||
{data?.notes.map((note) => (
|
||||
<li key={note.id}>
|
||||
<Group position="apart">
|
||||
<span>{note.content}</span>
|
||||
<ActionIcon
|
||||
size={21}
|
||||
onClick={(event) => {
|
||||
event.preventDefault()
|
||||
deleteNote(note.id)
|
||||
}}
|
||||
>
|
||||
<FaTrash />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Card>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@@ -75,6 +75,132 @@ export type StringComparisonExp = {
|
||||
_similar?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export enum AuthRefreshTokenTypesEnum {
|
||||
/** Personal access token */
|
||||
Pat = 'pat',
|
||||
/** Regular refresh token */
|
||||
Regular = 'regular'
|
||||
}
|
||||
|
||||
/** Boolean expression to compare columns of type "authRefreshTokenTypes_enum". All fields are combined with logical 'AND'. */
|
||||
export type AuthRefreshTokenTypesEnumComparisonExp = {
|
||||
_eq?: InputMaybe<AuthRefreshTokenTypesEnum>;
|
||||
_in?: InputMaybe<Array<AuthRefreshTokenTypesEnum>>;
|
||||
_is_null?: InputMaybe<Scalars['Boolean']>;
|
||||
_neq?: InputMaybe<AuthRefreshTokenTypesEnum>;
|
||||
_nin?: InputMaybe<Array<AuthRefreshTokenTypesEnum>>;
|
||||
};
|
||||
|
||||
/** User refresh tokens. Hasura auth uses them to rotate new access tokens as long as the refresh token is not expired. Don't modify its structure as Hasura Auth relies on it to function properly. */
|
||||
export type AuthRefreshTokens = {
|
||||
__typename?: 'authRefreshTokens';
|
||||
createdAt: Scalars['timestamptz'];
|
||||
expiresAt: Scalars['timestamptz'];
|
||||
id: Scalars['uuid'];
|
||||
metadata?: Maybe<Scalars['jsonb']>;
|
||||
type: AuthRefreshTokenTypesEnum;
|
||||
/** An object relationship */
|
||||
user: Users;
|
||||
userId: Scalars['uuid'];
|
||||
};
|
||||
|
||||
|
||||
/** User refresh tokens. Hasura auth uses them to rotate new access tokens as long as the refresh token is not expired. Don't modify its structure as Hasura Auth relies on it to function properly. */
|
||||
export type AuthRefreshTokensMetadataArgs = {
|
||||
path?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
/** order by aggregate values of table "auth.refresh_tokens" */
|
||||
export type AuthRefreshTokensAggregateOrderBy = {
|
||||
count?: InputMaybe<OrderBy>;
|
||||
max?: InputMaybe<AuthRefreshTokensMaxOrderBy>;
|
||||
min?: InputMaybe<AuthRefreshTokensMinOrderBy>;
|
||||
};
|
||||
|
||||
/** Boolean expression to filter rows from the table "auth.refresh_tokens". All fields are combined with a logical 'AND'. */
|
||||
export type AuthRefreshTokensBoolExp = {
|
||||
_and?: InputMaybe<Array<AuthRefreshTokensBoolExp>>;
|
||||
_not?: InputMaybe<AuthRefreshTokensBoolExp>;
|
||||
_or?: InputMaybe<Array<AuthRefreshTokensBoolExp>>;
|
||||
createdAt?: InputMaybe<TimestamptzComparisonExp>;
|
||||
expiresAt?: InputMaybe<TimestamptzComparisonExp>;
|
||||
id?: InputMaybe<UuidComparisonExp>;
|
||||
metadata?: InputMaybe<JsonbComparisonExp>;
|
||||
type?: InputMaybe<AuthRefreshTokenTypesEnumComparisonExp>;
|
||||
user?: InputMaybe<UsersBoolExp>;
|
||||
userId?: InputMaybe<UuidComparisonExp>;
|
||||
};
|
||||
|
||||
/** order by max() on columns of table "auth.refresh_tokens" */
|
||||
export type AuthRefreshTokensMaxOrderBy = {
|
||||
createdAt?: InputMaybe<OrderBy>;
|
||||
expiresAt?: InputMaybe<OrderBy>;
|
||||
id?: InputMaybe<OrderBy>;
|
||||
userId?: InputMaybe<OrderBy>;
|
||||
};
|
||||
|
||||
/** order by min() on columns of table "auth.refresh_tokens" */
|
||||
export type AuthRefreshTokensMinOrderBy = {
|
||||
createdAt?: InputMaybe<OrderBy>;
|
||||
expiresAt?: InputMaybe<OrderBy>;
|
||||
id?: InputMaybe<OrderBy>;
|
||||
userId?: InputMaybe<OrderBy>;
|
||||
};
|
||||
|
||||
/** response of any mutation on the table "auth.refresh_tokens" */
|
||||
export type AuthRefreshTokensMutationResponse = {
|
||||
__typename?: 'authRefreshTokens_mutation_response';
|
||||
/** number of rows affected by the mutation */
|
||||
affected_rows: Scalars['Int'];
|
||||
/** data from the rows affected by the mutation */
|
||||
returning: Array<AuthRefreshTokens>;
|
||||
};
|
||||
|
||||
/** Ordering options when selecting data from "auth.refresh_tokens". */
|
||||
export type AuthRefreshTokensOrderBy = {
|
||||
createdAt?: InputMaybe<OrderBy>;
|
||||
expiresAt?: InputMaybe<OrderBy>;
|
||||
id?: InputMaybe<OrderBy>;
|
||||
metadata?: InputMaybe<OrderBy>;
|
||||
type?: InputMaybe<OrderBy>;
|
||||
user?: InputMaybe<UsersOrderBy>;
|
||||
userId?: InputMaybe<OrderBy>;
|
||||
};
|
||||
|
||||
/** select columns of table "auth.refresh_tokens" */
|
||||
export enum AuthRefreshTokensSelectColumn {
|
||||
/** column name */
|
||||
CreatedAt = 'createdAt',
|
||||
/** column name */
|
||||
ExpiresAt = 'expiresAt',
|
||||
/** column name */
|
||||
Id = 'id',
|
||||
/** column name */
|
||||
Metadata = 'metadata',
|
||||
/** column name */
|
||||
Type = 'type',
|
||||
/** column name */
|
||||
UserId = 'userId'
|
||||
}
|
||||
|
||||
/** Streaming cursor of the table "authRefreshTokens" */
|
||||
export type AuthRefreshTokensStreamCursorInput = {
|
||||
/** Stream column input with initial value */
|
||||
initial_value: AuthRefreshTokensStreamCursorValueInput;
|
||||
/** cursor ordering */
|
||||
ordering?: InputMaybe<CursorOrdering>;
|
||||
};
|
||||
|
||||
/** Initial value of the column from where the streaming should start */
|
||||
export type AuthRefreshTokensStreamCursorValueInput = {
|
||||
createdAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
expiresAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
metadata?: InputMaybe<Scalars['jsonb']>;
|
||||
type?: InputMaybe<AuthRefreshTokenTypesEnum>;
|
||||
userId?: InputMaybe<Scalars['uuid']>;
|
||||
};
|
||||
|
||||
/** User webauthn security keys. Don't modify its structure as Hasura Auth relies on it to function properly. */
|
||||
export type AuthUserSecurityKeys = {
|
||||
__typename?: 'authUserSecurityKeys';
|
||||
@@ -144,6 +270,21 @@ export enum AuthUserSecurityKeysSelectColumn {
|
||||
UserId = 'userId'
|
||||
}
|
||||
|
||||
/** Streaming cursor of the table "authUserSecurityKeys" */
|
||||
export type AuthUserSecurityKeysStreamCursorInput = {
|
||||
/** Stream column input with initial value */
|
||||
initial_value: AuthUserSecurityKeysStreamCursorValueInput;
|
||||
/** cursor ordering */
|
||||
ordering?: InputMaybe<CursorOrdering>;
|
||||
};
|
||||
|
||||
/** Initial value of the column from where the streaming should start */
|
||||
export type AuthUserSecurityKeysStreamCursorValueInput = {
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
nickname?: InputMaybe<Scalars['String']>;
|
||||
userId?: InputMaybe<Scalars['uuid']>;
|
||||
};
|
||||
|
||||
/** Boolean expression to compare columns of type "citext". All fields are combined with logical 'AND'. */
|
||||
export type CitextComparisonExp = {
|
||||
_eq?: InputMaybe<Scalars['citext']>;
|
||||
@@ -177,6 +318,14 @@ export type CitextComparisonExp = {
|
||||
_similar?: InputMaybe<Scalars['citext']>;
|
||||
};
|
||||
|
||||
/** ordering argument of a cursor */
|
||||
export enum CursorOrdering {
|
||||
/** ascending ordering of the cursor */
|
||||
Asc = 'ASC',
|
||||
/** descending ordering of the cursor */
|
||||
Desc = 'DESC'
|
||||
}
|
||||
|
||||
/** columns and relationships of "storage.files" */
|
||||
export type Files = {
|
||||
__typename?: 'files';
|
||||
@@ -264,7 +413,7 @@ export type FilesOrderBy = {
|
||||
uploadedByUserId?: InputMaybe<OrderBy>;
|
||||
};
|
||||
|
||||
/** primary key columns input for table: files */
|
||||
/** primary key columns input for table: storage.files */
|
||||
export type FilesPkColumnsInput = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
@@ -307,6 +456,28 @@ export type FilesSetInput = {
|
||||
uploadedByUserId?: InputMaybe<Scalars['uuid']>;
|
||||
};
|
||||
|
||||
/** Streaming cursor of the table "files" */
|
||||
export type FilesStreamCursorInput = {
|
||||
/** Stream column input with initial value */
|
||||
initial_value: FilesStreamCursorValueInput;
|
||||
/** cursor ordering */
|
||||
ordering?: InputMaybe<CursorOrdering>;
|
||||
};
|
||||
|
||||
/** Initial value of the column from where the streaming should start */
|
||||
export type FilesStreamCursorValueInput = {
|
||||
bucketId?: InputMaybe<Scalars['String']>;
|
||||
createdAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
etag?: InputMaybe<Scalars['String']>;
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
isUploaded?: InputMaybe<Scalars['Boolean']>;
|
||||
mimeType?: InputMaybe<Scalars['String']>;
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
size?: InputMaybe<Scalars['Int']>;
|
||||
updatedAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
uploadedByUserId?: InputMaybe<Scalars['uuid']>;
|
||||
};
|
||||
|
||||
/** update columns of table "storage.files" */
|
||||
export enum FilesUpdateColumn {
|
||||
/** column name */
|
||||
@@ -336,6 +507,7 @@ export type FilesUpdates = {
|
||||
_inc?: InputMaybe<FilesIncInput>;
|
||||
/** sets the columns of the filtered rows to the given values */
|
||||
_set?: InputMaybe<FilesSetInput>;
|
||||
/** filter the rows which have to be updated */
|
||||
where: FilesBoolExp;
|
||||
};
|
||||
|
||||
@@ -370,6 +542,10 @@ export type JsonbComparisonExp = {
|
||||
/** mutation root */
|
||||
export type MutationRoot = {
|
||||
__typename?: 'mutation_root';
|
||||
/** delete single row from the table: "auth.refresh_tokens" */
|
||||
deleteAuthRefreshToken?: Maybe<AuthRefreshTokens>;
|
||||
/** delete data from the table: "auth.refresh_tokens" */
|
||||
deleteAuthRefreshTokens?: Maybe<AuthRefreshTokensMutationResponse>;
|
||||
/** delete single row from the table: "auth.user_security_keys" */
|
||||
deleteAuthUserSecurityKey?: Maybe<AuthUserSecurityKeys>;
|
||||
/** delete data from the table: "auth.user_security_keys" */
|
||||
@@ -378,14 +554,22 @@ export type MutationRoot = {
|
||||
deleteFile?: Maybe<Files>;
|
||||
/** delete data from the table: "storage.files" */
|
||||
deleteFiles?: Maybe<FilesMutationResponse>;
|
||||
/** delete single row from the table: "notes" */
|
||||
deleteNote?: Maybe<Notes>;
|
||||
/** delete data from the table: "notes" */
|
||||
deleteNotes?: Maybe<NotesMutationResponse>;
|
||||
/** delete single row from the table: "todos" */
|
||||
deleteTodo?: Maybe<Todos>;
|
||||
/** delete data from the table: "todos" */
|
||||
deleteTodos?: Maybe<TodosMutationResponse>;
|
||||
/** insert data into the table: "notes" */
|
||||
inserNotes?: Maybe<NotesMutationResponse>;
|
||||
/** insert a single row into the table: "storage.files" */
|
||||
insertFile?: Maybe<Files>;
|
||||
/** insert data into the table: "storage.files" */
|
||||
insertFiles?: Maybe<FilesMutationResponse>;
|
||||
/** insert a single row into the table: "notes" */
|
||||
insertNote?: Maybe<Notes>;
|
||||
/** insert a single row into the table: "todos" */
|
||||
insertTodo?: Maybe<Todos>;
|
||||
/** insert data into the table: "todos" */
|
||||
@@ -394,17 +578,35 @@ export type MutationRoot = {
|
||||
updateFile?: Maybe<Files>;
|
||||
/** update data of the table: "storage.files" */
|
||||
updateFiles?: Maybe<FilesMutationResponse>;
|
||||
/** update single row of the table: "notes" */
|
||||
updateNote?: Maybe<Notes>;
|
||||
/** update data of the table: "notes" */
|
||||
updateNotes?: Maybe<NotesMutationResponse>;
|
||||
/** update single row of the table: "todos" */
|
||||
updateTodo?: Maybe<Todos>;
|
||||
/** update data of the table: "todos" */
|
||||
updateTodos?: Maybe<TodosMutationResponse>;
|
||||
/** update multiples rows of table: "storage.files" */
|
||||
update_files_many?: Maybe<Array<Maybe<FilesMutationResponse>>>;
|
||||
/** update multiples rows of table: "notes" */
|
||||
update_notes_many?: Maybe<Array<Maybe<NotesMutationResponse>>>;
|
||||
/** update multiples rows of table: "todos" */
|
||||
update_todos_many?: Maybe<Array<Maybe<TodosMutationResponse>>>;
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootDeleteAuthRefreshTokenArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootDeleteAuthRefreshTokensArgs = {
|
||||
where: AuthRefreshTokensBoolExp;
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootDeleteAuthUserSecurityKeyArgs = {
|
||||
id: Scalars['uuid'];
|
||||
@@ -429,6 +631,18 @@ export type MutationRootDeleteFilesArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootDeleteNoteArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootDeleteNotesArgs = {
|
||||
where: NotesBoolExp;
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootDeleteTodoArgs = {
|
||||
id: Scalars['uuid'];
|
||||
@@ -441,6 +655,13 @@ export type MutationRootDeleteTodosArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootInserNotesArgs = {
|
||||
objects: Array<NotesInsertInput>;
|
||||
on_conflict?: InputMaybe<NotesOnConflict>;
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootInsertFileArgs = {
|
||||
object: FilesInsertInput;
|
||||
@@ -455,6 +676,13 @@ export type MutationRootInsertFilesArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootInsertNoteArgs = {
|
||||
object: NotesInsertInput;
|
||||
on_conflict?: InputMaybe<NotesOnConflict>;
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootInsertTodoArgs = {
|
||||
object: TodosInsertInput;
|
||||
@@ -485,6 +713,20 @@ export type MutationRootUpdateFilesArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootUpdateNoteArgs = {
|
||||
_set?: InputMaybe<NotesSetInput>;
|
||||
pk_columns: NotesPkColumnsInput;
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootUpdateNotesArgs = {
|
||||
_set?: InputMaybe<NotesSetInput>;
|
||||
where: NotesBoolExp;
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootUpdateTodoArgs = {
|
||||
_set?: InputMaybe<TodosSetInput>;
|
||||
@@ -505,11 +747,167 @@ export type MutationRootUpdateFilesManyArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootUpdateNotesManyArgs = {
|
||||
updates: Array<NotesUpdates>;
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type MutationRootUpdateTodosManyArgs = {
|
||||
updates: Array<TodosUpdates>;
|
||||
};
|
||||
|
||||
/** columns and relationships of "notes" */
|
||||
export type Notes = {
|
||||
__typename?: 'notes';
|
||||
content: Scalars['String'];
|
||||
createdAt: Scalars['timestamptz'];
|
||||
id: Scalars['uuid'];
|
||||
updatedAt: Scalars['timestamptz'];
|
||||
/** An object relationship */
|
||||
user: Users;
|
||||
};
|
||||
|
||||
/** aggregated selection of "notes" */
|
||||
export type NotesAggregate = {
|
||||
__typename?: 'notes_aggregate';
|
||||
aggregate?: Maybe<NotesAggregateFields>;
|
||||
nodes: Array<Notes>;
|
||||
};
|
||||
|
||||
/** aggregate fields of "notes" */
|
||||
export type NotesAggregateFields = {
|
||||
__typename?: 'notes_aggregate_fields';
|
||||
count: Scalars['Int'];
|
||||
max?: Maybe<NotesMaxFields>;
|
||||
min?: Maybe<NotesMinFields>;
|
||||
};
|
||||
|
||||
|
||||
/** aggregate fields of "notes" */
|
||||
export type NotesAggregateFieldsCountArgs = {
|
||||
columns?: InputMaybe<Array<NotesSelectColumn>>;
|
||||
distinct?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
/** Boolean expression to filter rows from the table "notes". All fields are combined with a logical 'AND'. */
|
||||
export type NotesBoolExp = {
|
||||
_and?: InputMaybe<Array<NotesBoolExp>>;
|
||||
_not?: InputMaybe<NotesBoolExp>;
|
||||
_or?: InputMaybe<Array<NotesBoolExp>>;
|
||||
content?: InputMaybe<StringComparisonExp>;
|
||||
createdAt?: InputMaybe<TimestamptzComparisonExp>;
|
||||
id?: InputMaybe<UuidComparisonExp>;
|
||||
updatedAt?: InputMaybe<TimestamptzComparisonExp>;
|
||||
user?: InputMaybe<UsersBoolExp>;
|
||||
};
|
||||
|
||||
/** unique or primary key constraints on table "notes" */
|
||||
export enum NotesConstraint {
|
||||
/** unique or primary key constraint on columns "id" */
|
||||
NotesPkey = 'notes_pkey'
|
||||
}
|
||||
|
||||
/** input type for inserting data into table "notes" */
|
||||
export type NotesInsertInput = {
|
||||
content?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
/** aggregate max on columns */
|
||||
export type NotesMaxFields = {
|
||||
__typename?: 'notes_max_fields';
|
||||
content?: Maybe<Scalars['String']>;
|
||||
createdAt?: Maybe<Scalars['timestamptz']>;
|
||||
id?: Maybe<Scalars['uuid']>;
|
||||
updatedAt?: Maybe<Scalars['timestamptz']>;
|
||||
};
|
||||
|
||||
/** aggregate min on columns */
|
||||
export type NotesMinFields = {
|
||||
__typename?: 'notes_min_fields';
|
||||
content?: Maybe<Scalars['String']>;
|
||||
createdAt?: Maybe<Scalars['timestamptz']>;
|
||||
id?: Maybe<Scalars['uuid']>;
|
||||
updatedAt?: Maybe<Scalars['timestamptz']>;
|
||||
};
|
||||
|
||||
/** response of any mutation on the table "notes" */
|
||||
export type NotesMutationResponse = {
|
||||
__typename?: 'notes_mutation_response';
|
||||
/** number of rows affected by the mutation */
|
||||
affected_rows: Scalars['Int'];
|
||||
/** data from the rows affected by the mutation */
|
||||
returning: Array<Notes>;
|
||||
};
|
||||
|
||||
/** on_conflict condition type for table "notes" */
|
||||
export type NotesOnConflict = {
|
||||
constraint: NotesConstraint;
|
||||
update_columns?: Array<NotesUpdateColumn>;
|
||||
where?: InputMaybe<NotesBoolExp>;
|
||||
};
|
||||
|
||||
/** Ordering options when selecting data from "notes". */
|
||||
export type NotesOrderBy = {
|
||||
content?: InputMaybe<OrderBy>;
|
||||
createdAt?: InputMaybe<OrderBy>;
|
||||
id?: InputMaybe<OrderBy>;
|
||||
updatedAt?: InputMaybe<OrderBy>;
|
||||
user?: InputMaybe<UsersOrderBy>;
|
||||
};
|
||||
|
||||
/** primary key columns input for table: notes */
|
||||
export type NotesPkColumnsInput = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
|
||||
/** select columns of table "notes" */
|
||||
export enum NotesSelectColumn {
|
||||
/** column name */
|
||||
Content = 'content',
|
||||
/** column name */
|
||||
CreatedAt = 'createdAt',
|
||||
/** column name */
|
||||
Id = 'id',
|
||||
/** column name */
|
||||
UpdatedAt = 'updatedAt'
|
||||
}
|
||||
|
||||
/** input type for updating data in table "notes" */
|
||||
export type NotesSetInput = {
|
||||
content?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
/** Streaming cursor of the table "notes" */
|
||||
export type NotesStreamCursorInput = {
|
||||
/** Stream column input with initial value */
|
||||
initial_value: NotesStreamCursorValueInput;
|
||||
/** cursor ordering */
|
||||
ordering?: InputMaybe<CursorOrdering>;
|
||||
};
|
||||
|
||||
/** Initial value of the column from where the streaming should start */
|
||||
export type NotesStreamCursorValueInput = {
|
||||
content?: InputMaybe<Scalars['String']>;
|
||||
createdAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
updatedAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
};
|
||||
|
||||
/** update columns of table "notes" */
|
||||
export enum NotesUpdateColumn {
|
||||
/** column name */
|
||||
Content = 'content'
|
||||
}
|
||||
|
||||
export type NotesUpdates = {
|
||||
/** sets the columns of the filtered rows to the given values */
|
||||
_set?: InputMaybe<NotesSetInput>;
|
||||
/** filter the rows which have to be updated */
|
||||
where: NotesBoolExp;
|
||||
};
|
||||
|
||||
/** column ordering options */
|
||||
export enum OrderBy {
|
||||
/** in ascending order, nulls last */
|
||||
@@ -528,6 +926,10 @@ export enum OrderBy {
|
||||
|
||||
export type QueryRoot = {
|
||||
__typename?: 'query_root';
|
||||
/** fetch data from the table: "auth.refresh_tokens" using primary key columns */
|
||||
authRefreshToken?: Maybe<AuthRefreshTokens>;
|
||||
/** fetch data from the table: "auth.refresh_tokens" */
|
||||
authRefreshTokens: Array<AuthRefreshTokens>;
|
||||
/** fetch data from the table: "auth.user_security_keys" using primary key columns */
|
||||
authUserSecurityKey?: Maybe<AuthUserSecurityKeys>;
|
||||
/** fetch data from the table: "auth.user_security_keys" */
|
||||
@@ -536,6 +938,12 @@ export type QueryRoot = {
|
||||
file?: Maybe<Files>;
|
||||
/** fetch data from the table: "storage.files" */
|
||||
files: Array<Files>;
|
||||
/** fetch data from the table: "notes" using primary key columns */
|
||||
note?: Maybe<Notes>;
|
||||
/** fetch data from the table: "notes" */
|
||||
notes: Array<Notes>;
|
||||
/** fetch aggregated fields from the table: "notes" */
|
||||
notesAggregate: NotesAggregate;
|
||||
/** fetch data from the table: "todos" using primary key columns */
|
||||
todo?: Maybe<Todos>;
|
||||
/** fetch data from the table: "todos" */
|
||||
@@ -549,6 +957,20 @@ export type QueryRoot = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryRootAuthRefreshTokenArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryRootAuthRefreshTokensArgs = {
|
||||
distinct_on?: InputMaybe<Array<AuthRefreshTokensSelectColumn>>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
offset?: InputMaybe<Scalars['Int']>;
|
||||
order_by?: InputMaybe<Array<AuthRefreshTokensOrderBy>>;
|
||||
where?: InputMaybe<AuthRefreshTokensBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryRootAuthUserSecurityKeyArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
@@ -577,6 +999,29 @@ export type QueryRootFilesArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryRootNoteArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryRootNotesArgs = {
|
||||
distinct_on?: InputMaybe<Array<NotesSelectColumn>>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
offset?: InputMaybe<Scalars['Int']>;
|
||||
order_by?: InputMaybe<Array<NotesOrderBy>>;
|
||||
where?: InputMaybe<NotesBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryRootNotesAggregateArgs = {
|
||||
distinct_on?: InputMaybe<Array<NotesSelectColumn>>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
offset?: InputMaybe<Scalars['Int']>;
|
||||
order_by?: InputMaybe<Array<NotesOrderBy>>;
|
||||
where?: InputMaybe<NotesBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryRootTodoArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
@@ -615,24 +1060,67 @@ export type QueryRootUsersArgs = {
|
||||
|
||||
export type SubscriptionRoot = {
|
||||
__typename?: 'subscription_root';
|
||||
/** fetch data from the table: "auth.refresh_tokens" using primary key columns */
|
||||
authRefreshToken?: Maybe<AuthRefreshTokens>;
|
||||
/** fetch data from the table: "auth.refresh_tokens" */
|
||||
authRefreshTokens: Array<AuthRefreshTokens>;
|
||||
/** fetch data from the table in a streaming manner: "auth.refresh_tokens" */
|
||||
authRefreshTokens_stream: Array<AuthRefreshTokens>;
|
||||
/** fetch data from the table: "auth.user_security_keys" using primary key columns */
|
||||
authUserSecurityKey?: Maybe<AuthUserSecurityKeys>;
|
||||
/** fetch data from the table: "auth.user_security_keys" */
|
||||
authUserSecurityKeys: Array<AuthUserSecurityKeys>;
|
||||
/** fetch data from the table in a streaming manner: "auth.user_security_keys" */
|
||||
authUserSecurityKeys_stream: Array<AuthUserSecurityKeys>;
|
||||
/** fetch data from the table: "storage.files" using primary key columns */
|
||||
file?: Maybe<Files>;
|
||||
/** fetch data from the table: "storage.files" */
|
||||
files: Array<Files>;
|
||||
/** fetch data from the table in a streaming manner: "storage.files" */
|
||||
files_stream: Array<Files>;
|
||||
/** fetch data from the table: "notes" using primary key columns */
|
||||
note?: Maybe<Notes>;
|
||||
/** fetch data from the table: "notes" */
|
||||
notes: Array<Notes>;
|
||||
/** fetch aggregated fields from the table: "notes" */
|
||||
notesAggregate: NotesAggregate;
|
||||
/** fetch data from the table in a streaming manner: "notes" */
|
||||
notes_stream: Array<Notes>;
|
||||
/** fetch data from the table: "todos" using primary key columns */
|
||||
todo?: Maybe<Todos>;
|
||||
/** fetch data from the table: "todos" */
|
||||
todos: Array<Todos>;
|
||||
/** fetch aggregated fields from the table: "todos" */
|
||||
todosAggregate: TodosAggregate;
|
||||
/** fetch data from the table in a streaming manner: "todos" */
|
||||
todos_stream: Array<Todos>;
|
||||
/** fetch data from the table: "auth.users" using primary key columns */
|
||||
user?: Maybe<Users>;
|
||||
/** fetch data from the table: "auth.users" */
|
||||
users: Array<Users>;
|
||||
/** fetch data from the table in a streaming manner: "auth.users" */
|
||||
users_stream: Array<Users>;
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootAuthRefreshTokenArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootAuthRefreshTokensArgs = {
|
||||
distinct_on?: InputMaybe<Array<AuthRefreshTokensSelectColumn>>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
offset?: InputMaybe<Scalars['Int']>;
|
||||
order_by?: InputMaybe<Array<AuthRefreshTokensOrderBy>>;
|
||||
where?: InputMaybe<AuthRefreshTokensBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootAuthRefreshTokensStreamArgs = {
|
||||
batch_size: Scalars['Int'];
|
||||
cursor: Array<InputMaybe<AuthRefreshTokensStreamCursorInput>>;
|
||||
where?: InputMaybe<AuthRefreshTokensBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
@@ -650,6 +1138,13 @@ export type SubscriptionRootAuthUserSecurityKeysArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootAuthUserSecurityKeysStreamArgs = {
|
||||
batch_size: Scalars['Int'];
|
||||
cursor: Array<InputMaybe<AuthUserSecurityKeysStreamCursorInput>>;
|
||||
where?: InputMaybe<AuthUserSecurityKeysBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootFileArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
@@ -664,6 +1159,43 @@ export type SubscriptionRootFilesArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootFilesStreamArgs = {
|
||||
batch_size: Scalars['Int'];
|
||||
cursor: Array<InputMaybe<FilesStreamCursorInput>>;
|
||||
where?: InputMaybe<FilesBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootNoteArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootNotesArgs = {
|
||||
distinct_on?: InputMaybe<Array<NotesSelectColumn>>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
offset?: InputMaybe<Scalars['Int']>;
|
||||
order_by?: InputMaybe<Array<NotesOrderBy>>;
|
||||
where?: InputMaybe<NotesBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootNotesAggregateArgs = {
|
||||
distinct_on?: InputMaybe<Array<NotesSelectColumn>>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
offset?: InputMaybe<Scalars['Int']>;
|
||||
order_by?: InputMaybe<Array<NotesOrderBy>>;
|
||||
where?: InputMaybe<NotesBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootNotesStreamArgs = {
|
||||
batch_size: Scalars['Int'];
|
||||
cursor: Array<InputMaybe<NotesStreamCursorInput>>;
|
||||
where?: InputMaybe<NotesBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootTodoArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
@@ -687,6 +1219,13 @@ export type SubscriptionRootTodosAggregateArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootTodosStreamArgs = {
|
||||
batch_size: Scalars['Int'];
|
||||
cursor: Array<InputMaybe<TodosStreamCursorInput>>;
|
||||
where?: InputMaybe<TodosBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootUserArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
@@ -700,6 +1239,13 @@ export type SubscriptionRootUsersArgs = {
|
||||
where?: InputMaybe<UsersBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootUsersStreamArgs = {
|
||||
batch_size: Scalars['Int'];
|
||||
cursor: Array<InputMaybe<UsersStreamCursorInput>>;
|
||||
where?: InputMaybe<UsersBoolExp>;
|
||||
};
|
||||
|
||||
/** Boolean expression to compare columns of type "timestamptz". All fields are combined with logical 'AND'. */
|
||||
export type TimestamptzComparisonExp = {
|
||||
_eq?: InputMaybe<Scalars['timestamptz']>;
|
||||
@@ -842,6 +1388,23 @@ export type TodosSetInput = {
|
||||
contents?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
/** Streaming cursor of the table "todos" */
|
||||
export type TodosStreamCursorInput = {
|
||||
/** Stream column input with initial value */
|
||||
initial_value: TodosStreamCursorValueInput;
|
||||
/** cursor ordering */
|
||||
ordering?: InputMaybe<CursorOrdering>;
|
||||
};
|
||||
|
||||
/** Initial value of the column from where the streaming should start */
|
||||
export type TodosStreamCursorValueInput = {
|
||||
contents?: InputMaybe<Scalars['String']>;
|
||||
createdAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
updatedAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
userId?: InputMaybe<Scalars['uuid']>;
|
||||
};
|
||||
|
||||
/** update columns of table "todos" */
|
||||
export enum TodosUpdateColumn {
|
||||
/** column name */
|
||||
@@ -851,6 +1414,7 @@ export enum TodosUpdateColumn {
|
||||
export type TodosUpdates = {
|
||||
/** sets the columns of the filtered rows to the given values */
|
||||
_set?: InputMaybe<TodosSetInput>;
|
||||
/** filter the rows which have to be updated */
|
||||
where: TodosBoolExp;
|
||||
};
|
||||
|
||||
@@ -876,6 +1440,8 @@ export type Users = {
|
||||
phoneNumber?: Maybe<Scalars['String']>;
|
||||
phoneNumberVerified: Scalars['Boolean'];
|
||||
/** An array relationship */
|
||||
refreshTokens: Array<AuthRefreshTokens>;
|
||||
/** An array relationship */
|
||||
securityKeys: Array<AuthUserSecurityKeys>;
|
||||
updatedAt: Scalars['timestamptz'];
|
||||
};
|
||||
@@ -887,6 +1453,16 @@ export type UsersMetadataArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** User account information. Don't modify its structure as Hasura Auth relies on it to function properly. */
|
||||
export type UsersRefreshTokensArgs = {
|
||||
distinct_on?: InputMaybe<Array<AuthRefreshTokensSelectColumn>>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
offset?: InputMaybe<Scalars['Int']>;
|
||||
order_by?: InputMaybe<Array<AuthRefreshTokensOrderBy>>;
|
||||
where?: InputMaybe<AuthRefreshTokensBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
/** User account information. Don't modify its structure as Hasura Auth relies on it to function properly. */
|
||||
export type UsersSecurityKeysArgs = {
|
||||
distinct_on?: InputMaybe<Array<AuthUserSecurityKeysSelectColumn>>;
|
||||
@@ -919,6 +1495,7 @@ export type UsersBoolExp = {
|
||||
otpMethodLastUsed?: InputMaybe<StringComparisonExp>;
|
||||
phoneNumber?: InputMaybe<StringComparisonExp>;
|
||||
phoneNumberVerified?: InputMaybe<BooleanComparisonExp>;
|
||||
refreshTokens?: InputMaybe<AuthRefreshTokensBoolExp>;
|
||||
securityKeys?: InputMaybe<AuthUserSecurityKeysBoolExp>;
|
||||
updatedAt?: InputMaybe<TimestamptzComparisonExp>;
|
||||
};
|
||||
@@ -943,6 +1520,7 @@ export type UsersOrderBy = {
|
||||
otpMethodLastUsed?: InputMaybe<OrderBy>;
|
||||
phoneNumber?: InputMaybe<OrderBy>;
|
||||
phoneNumberVerified?: InputMaybe<OrderBy>;
|
||||
refreshTokens_aggregate?: InputMaybe<AuthRefreshTokensAggregateOrderBy>;
|
||||
securityKeys_aggregate?: InputMaybe<AuthUserSecurityKeysAggregateOrderBy>;
|
||||
updatedAt?: InputMaybe<OrderBy>;
|
||||
};
|
||||
@@ -989,6 +1567,37 @@ export enum UsersSelectColumn {
|
||||
UpdatedAt = 'updatedAt'
|
||||
}
|
||||
|
||||
/** Streaming cursor of the table "users" */
|
||||
export type UsersStreamCursorInput = {
|
||||
/** Stream column input with initial value */
|
||||
initial_value: UsersStreamCursorValueInput;
|
||||
/** cursor ordering */
|
||||
ordering?: InputMaybe<CursorOrdering>;
|
||||
};
|
||||
|
||||
/** Initial value of the column from where the streaming should start */
|
||||
export type UsersStreamCursorValueInput = {
|
||||
activeMfaType?: InputMaybe<Scalars['String']>;
|
||||
avatarUrl?: InputMaybe<Scalars['String']>;
|
||||
createdAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
currentChallenge?: InputMaybe<Scalars['String']>;
|
||||
defaultRole?: InputMaybe<Scalars['String']>;
|
||||
disabled?: InputMaybe<Scalars['Boolean']>;
|
||||
displayName?: InputMaybe<Scalars['String']>;
|
||||
email?: InputMaybe<Scalars['citext']>;
|
||||
emailVerified?: InputMaybe<Scalars['Boolean']>;
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
isAnonymous?: InputMaybe<Scalars['Boolean']>;
|
||||
lastSeen?: InputMaybe<Scalars['timestamptz']>;
|
||||
locale?: InputMaybe<Scalars['String']>;
|
||||
metadata?: InputMaybe<Scalars['jsonb']>;
|
||||
otpHash?: InputMaybe<Scalars['String']>;
|
||||
otpMethodLastUsed?: InputMaybe<Scalars['String']>;
|
||||
phoneNumber?: InputMaybe<Scalars['String']>;
|
||||
phoneNumberVerified?: InputMaybe<Scalars['Boolean']>;
|
||||
updatedAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
};
|
||||
|
||||
/** Boolean expression to compare columns of type "uuid". All fields are combined with logical 'AND'. */
|
||||
export type UuidComparisonExp = {
|
||||
_eq?: InputMaybe<Scalars['uuid']>;
|
||||
@@ -1016,6 +1625,27 @@ export type AddItemMutation = { __typename?: 'mutation_root', insertTodo?: { __t
|
||||
|
||||
export type NewTodoFragment = { __typename?: 'todos', id: string, contents: string };
|
||||
|
||||
export type NotesListQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type NotesListQuery = { __typename?: 'query_root', notes: Array<{ __typename?: 'notes', id: string, content: string }> };
|
||||
|
||||
export type InsertNoteMutationVariables = Exact<{
|
||||
content: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type InsertNoteMutation = { __typename?: 'mutation_root', insertNote?: { __typename?: 'notes', id: string, content: string } | null };
|
||||
|
||||
export type DeleteNoteMutationVariables = Exact<{
|
||||
noteId: Scalars['uuid'];
|
||||
}>;
|
||||
|
||||
|
||||
export type DeleteNoteMutation = { __typename?: 'mutation_root', deleteNote?: { __typename?: 'notes', id: string, content: string } | null };
|
||||
|
||||
export type NewNoteFragment = { __typename?: 'notes', id: string, content: string };
|
||||
|
||||
export type SecurityKeysQueryVariables = Exact<{
|
||||
userId: Scalars['uuid'];
|
||||
}>;
|
||||
|
||||
@@ -2,11 +2,25 @@ import { useState } from 'react'
|
||||
|
||||
import { Button, Card, Grid, TextInput, Title } from '@mantine/core'
|
||||
import { showNotification } from '@mantine/notifications'
|
||||
import { useChangeEmail, useUserEmail } from '@nhost/react'
|
||||
import { useChangeEmail, useElevateSecurityKeyEmail, useUserEmail, useUserId } from '@nhost/react'
|
||||
import { useAuthQuery } from '@nhost/react-apollo'
|
||||
import { SecurityKeysQuery } from 'src/generated'
|
||||
import { SECURITY_KEYS_LIST } from 'src/utils'
|
||||
|
||||
export const ChangeEmail: React.FC = () => {
|
||||
const [newEmail, setNewEmail] = useState('')
|
||||
const userId = useUserId()
|
||||
const email = useUserEmail()
|
||||
const [newEmail, setNewEmail] = useState('')
|
||||
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
|
||||
const [userHasSecurityKey, setUserHasSecurityKey] = useState(false)
|
||||
|
||||
useAuthQuery<SecurityKeysQuery>(SECURITY_KEYS_LIST, {
|
||||
variables: { userId },
|
||||
onCompleted: ({ authUserSecurityKeys }) => {
|
||||
setUserHasSecurityKey(authUserSecurityKeys?.length > 0)
|
||||
}
|
||||
})
|
||||
|
||||
const { changeEmail } = useChangeEmail({
|
||||
redirectTo: '/profile'
|
||||
})
|
||||
@@ -19,7 +33,26 @@ export const ChangeEmail: React.FC = () => {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!elevated && userHasSecurityKey) {
|
||||
try {
|
||||
const { elevated } = await elevateEmailSecurityKey(email as string)
|
||||
|
||||
if (!elevated) {
|
||||
throw new Error('Permissions were not elevated')
|
||||
}
|
||||
} catch (error) {
|
||||
showNotification({
|
||||
title: 'Error',
|
||||
message: 'Could not elevate permissions'
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const result = await changeEmail(newEmail)
|
||||
|
||||
if (result.needsEmailVerification) {
|
||||
showNotification({
|
||||
message: `An email has been sent to ${newEmail}. Please check your inbox and follow the link to confirm the email change.`
|
||||
@@ -33,6 +66,7 @@ export const ChangeEmail: React.FC = () => {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card shadow="sm" p="lg" m="sm">
|
||||
<Title>Change email</Title>
|
||||
|
||||
@@ -1,14 +1,53 @@
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { Button, Card, Grid, PasswordInput, Title } from '@mantine/core'
|
||||
import { showNotification } from '@mantine/notifications'
|
||||
import { useChangePassword } from '@nhost/react'
|
||||
import {
|
||||
useChangePassword,
|
||||
useElevateSecurityKeyEmail,
|
||||
useUserEmail,
|
||||
useUserId
|
||||
} from '@nhost/react'
|
||||
import { SecurityKeysQuery } from 'src/generated'
|
||||
import { SECURITY_KEYS_LIST } from 'src/utils'
|
||||
import { useAuthQuery } from '@nhost/react-apollo'
|
||||
|
||||
export const ChangePassword: React.FC = () => {
|
||||
const userEmail = useUserEmail()
|
||||
const userId = useUserId()
|
||||
const [password, setPassword] = useState('')
|
||||
const { changePassword } = useChangePassword()
|
||||
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
|
||||
const [userHasSecurityKey, setUserHasSecurityKey] = useState(false)
|
||||
|
||||
const { data } = useAuthQuery<SecurityKeysQuery>(SECURITY_KEYS_LIST, { variables: { userId } })
|
||||
|
||||
useEffect(() => {
|
||||
const authUserSecurityKeys = data?.authUserSecurityKeys
|
||||
|
||||
if (authUserSecurityKeys) {
|
||||
setUserHasSecurityKey(authUserSecurityKeys.length > 0)
|
||||
}
|
||||
}, [data])
|
||||
|
||||
const change = async () => {
|
||||
if (!elevated && userHasSecurityKey) {
|
||||
try {
|
||||
const { elevated } = await elevateEmailSecurityKey(userEmail as string)
|
||||
|
||||
if (!elevated) {
|
||||
throw new Error('Permissions were not elevated')
|
||||
}
|
||||
} catch (error) {
|
||||
showNotification({
|
||||
title: 'Error',
|
||||
message: 'Could not elevate permissions'
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const result = await changePassword(password)
|
||||
if (result.isSuccess) {
|
||||
showNotification({
|
||||
|
||||
@@ -2,11 +2,56 @@ import { useState } from 'react'
|
||||
|
||||
import { Button, Card, TextInput, Title } from '@mantine/core'
|
||||
import { showNotification } from '@mantine/notifications'
|
||||
import { useConfigMfa } from '@nhost/react'
|
||||
import { useConfigMfa, useElevateSecurityKeyEmail, useUserEmail, useUserId } from '@nhost/react'
|
||||
import { SECURITY_KEYS_LIST } from 'src/utils'
|
||||
import { SecurityKeysQuery } from 'src/generated'
|
||||
import { useAuthQuery } from '@nhost/react-apollo'
|
||||
|
||||
export const Mfa: React.FC = () => {
|
||||
const userId = useUserId()
|
||||
const userEmail = useUserEmail()
|
||||
const [code, setCode] = useState('')
|
||||
const { generateQrCode, activateMfa, isActivated, isGenerated, qrCodeDataUrl } = useConfigMfa()
|
||||
|
||||
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
|
||||
const [userHasSecurityKey, setUserHasSecurityKey] = useState(false)
|
||||
|
||||
useAuthQuery<SecurityKeysQuery>(SECURITY_KEYS_LIST, {
|
||||
variables: { userId },
|
||||
onCompleted: ({ authUserSecurityKeys }) => {
|
||||
setUserHasSecurityKey(authUserSecurityKeys?.length > 0)
|
||||
}
|
||||
})
|
||||
|
||||
const activate = async () => {
|
||||
if (!elevated && userHasSecurityKey) {
|
||||
try {
|
||||
const { elevated } = await elevateEmailSecurityKey(userEmail as string)
|
||||
|
||||
if (!elevated) {
|
||||
throw new Error('Permissions were not elevated')
|
||||
}
|
||||
} catch (error) {
|
||||
showNotification({
|
||||
title: 'Error',
|
||||
message: 'Could not elevate permissions'
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const { error, isError } = await activateMfa(code)
|
||||
|
||||
if (isError) {
|
||||
showNotification({
|
||||
color: 'red',
|
||||
title: 'Error',
|
||||
message: error?.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const generate = async () => {
|
||||
const result = await generateQrCode()
|
||||
if (result.error) {
|
||||
@@ -33,7 +78,7 @@ export const Mfa: React.FC = () => {
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
placeholder="Enter activation code"
|
||||
/>
|
||||
<Button fullWidth onClick={() => activateMfa(code)}>
|
||||
<Button fullWidth onClick={activate}>
|
||||
Activate
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -2,36 +2,22 @@ import { useState } from 'react'
|
||||
import { FaMinus } from 'react-icons/fa'
|
||||
import { RemoveSecurityKeyMutation, SecurityKeysQuery } from 'src/generated'
|
||||
|
||||
import { gql, useMutation } from '@apollo/client'
|
||||
import { ApolloError, useApolloClient, useMutation } from '@apollo/client'
|
||||
import { ActionIcon, Button, Card, SimpleGrid, Table, TextInput, Title } from '@mantine/core'
|
||||
import { useInputState } from '@mantine/hooks'
|
||||
import { showNotification } from '@mantine/notifications'
|
||||
import { useAddSecurityKey, useUserId } from '@nhost/react'
|
||||
import { useAuthQuery } from '@nhost/react-apollo'
|
||||
|
||||
const SECURITY_KEYS_LIST = gql`
|
||||
query securityKeys($userId: uuid!) {
|
||||
authUserSecurityKeys(where: { userId: { _eq: $userId } }) {
|
||||
id
|
||||
nickname
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const REMOVE_SECURITY_KEY = gql`
|
||||
mutation removeSecurityKey($id: uuid!) {
|
||||
deleteAuthUserSecurityKey(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
import { REMOVE_SECURITY_KEY, SECURITY_KEYS_LIST } from 'src/utils'
|
||||
|
||||
export const SecurityKeys: React.FC = () => {
|
||||
const { add } = useAddSecurityKey()
|
||||
const userId = useUserId()
|
||||
const client = useApolloClient()
|
||||
const { add } = useAddSecurityKey()
|
||||
// Nickname of the security key
|
||||
const [nickname, setNickname] = useInputState('')
|
||||
const [list, setList] = useState<{ id: string; nickname?: string | null }[]>([])
|
||||
|
||||
useAuthQuery<SecurityKeysQuery>(SECURITY_KEYS_LIST, {
|
||||
variables: { userId },
|
||||
onCompleted: ({ authUserSecurityKeys }) => {
|
||||
@@ -43,9 +29,10 @@ export const SecurityKeys: React.FC = () => {
|
||||
|
||||
const addKey = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
|
||||
const { key, isError, error } = await add(nickname)
|
||||
|
||||
if (isError) {
|
||||
console.log(error)
|
||||
showNotification({
|
||||
color: 'red',
|
||||
title: 'Error',
|
||||
@@ -53,11 +40,17 @@ export const SecurityKeys: React.FC = () => {
|
||||
})
|
||||
} else {
|
||||
setNickname('')
|
||||
|
||||
// refetch securityKeys so that we know if need to elevate in other components
|
||||
await client.refetchQueries({
|
||||
include: [SECURITY_KEYS_LIST]
|
||||
})
|
||||
}
|
||||
if (key) {
|
||||
setList([...list, key])
|
||||
}
|
||||
}
|
||||
|
||||
const [removeKey] = useMutation<RemoveSecurityKeyMutation>(REMOVE_SECURITY_KEY, {
|
||||
onCompleted: ({ deleteAuthUserSecurityKey }) => {
|
||||
if (deleteAuthUserSecurityKey?.id) {
|
||||
@@ -66,6 +59,25 @@ export const SecurityKeys: React.FC = () => {
|
||||
}
|
||||
})
|
||||
|
||||
const handleRemoveKey = async (id: string) => {
|
||||
try {
|
||||
await removeKey({ variables: { id } })
|
||||
|
||||
// refetch securityKeys so that we know if need to elevate in other components
|
||||
await client.refetchQueries({
|
||||
include: [SECURITY_KEYS_LIST]
|
||||
})
|
||||
} catch (error) {
|
||||
const e = error as ApolloError
|
||||
|
||||
showNotification({
|
||||
color: 'red',
|
||||
title: 'Error',
|
||||
message: e?.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card shadow="sm" p="lg" m="sm">
|
||||
<Title>Security keys</Title>
|
||||
@@ -79,7 +91,7 @@ export const SecurityKeys: React.FC = () => {
|
||||
<tr key={id}>
|
||||
<td>{nickname || id}</td>
|
||||
<td>
|
||||
<ActionIcon onClick={() => removeKey({ variables: { id } })} color="red">
|
||||
<ActionIcon onClick={() => handleRemoveKey(id)} color="red">
|
||||
<FaMinus />
|
||||
</ActionIcon>
|
||||
</td>
|
||||
|
||||
1
examples/react-apollo/src/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './security-keys'
|
||||
18
examples/react-apollo/src/utils/security-keys.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { gql } from '@apollo/client'
|
||||
|
||||
export const SECURITY_KEYS_LIST = gql`
|
||||
query securityKeys($userId: uuid!) {
|
||||
authUserSecurityKeys(where: { userId: { _eq: $userId } }) {
|
||||
id
|
||||
nickname
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const REMOVE_SECURITY_KEY = gql`
|
||||
mutation removeSecurityKey($id: uuid!) {
|
||||
deleteAuthUserSecurityKey(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
import path from 'path'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
@@ -8,5 +8,10 @@ export default defineConfig({
|
||||
include: ['react/jsx-runtime'],
|
||||
exclude: ['@nhost/react']
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
src: path.resolve(__dirname, './src')
|
||||
}
|
||||
},
|
||||
plugins: [react()]
|
||||
})
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# @nhost-examples/react-gqty
|
||||
|
||||
## 1.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [017f1a6]
|
||||
- @nhost/react@3.2.0
|
||||
|
||||
## 1.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.1.1
|
||||
|
||||
## 1.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e5bab6a: chore: update dependencies
|
||||
- Updated dependencies [1a61c65]
|
||||
- Updated dependencies [e5bab6a]
|
||||
- @nhost/react@3.1.0
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-gqty",
|
||||
"private": true,
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@gqty/react": "2.1.0",
|
||||
"@nhost/react": "^3.0.2",
|
||||
"@nhost/react": "workspace:^",
|
||||
"gqty": "^2.3.0",
|
||||
"graphql": "16.8.1",
|
||||
"react": "18.2.0",
|
||||
@@ -20,10 +20,10 @@
|
||||
"devDependencies": {
|
||||
"@gqty/cli": "3.3.0-alpha-d8cdbf6.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@types/react": "^18.2.47",
|
||||
"@types/react": "^18.2.50",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.33",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^4.9.5",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @nhost-examples/serverless-functions
|
||||
|
||||
## 0.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8c34c69: chore: update nodemailer version to `6.9.9`
|
||||
- Updated dependencies [e5bab6a]
|
||||
- @nhost/stripe-graphql-js@1.0.7
|
||||
|
||||
## 0.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/serverless-functions",
|
||||
"private": true,
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.11",
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21"
|
||||
},
|
||||
@@ -11,7 +11,7 @@
|
||||
"@pothos/core": "^3.41.0",
|
||||
"cross-fetch": "^3.1.8",
|
||||
"graphql": "16.8.1",
|
||||
"nodemailer": "^6.9.8",
|
||||
"nodemailer": "^6.9.9",
|
||||
"slugify": "^1.6.6",
|
||||
"stripe": "^11.18.0"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# @nhost-examples/vue-apollo
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 017f1a6: feat: add elevated permission examples
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [017f1a6]
|
||||
- @nhost/vue@2.2.0
|
||||
- @nhost/nhost-js@3.0.5
|
||||
- @nhost/apollo@6.0.5
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.4
|
||||
- @nhost/apollo@6.0.4
|
||||
- @nhost/vue@2.1.1
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 1a61c65: feat: add 'elevateEmailSecurityKey' to the SDKs along with integration into react-apollo and vue-apollo examples
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e5bab6a: chore: update dependencies
|
||||
- Updated dependencies [1a61c65]
|
||||
- Updated dependencies [e5bab6a]
|
||||
- @nhost/vue@2.1.0
|
||||
- @nhost/apollo@6.0.3
|
||||
- @nhost/nhost-js@3.0.3
|
||||
|
||||
## 0.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
1
examples/vue-apollo/nhost/metadata/backend_configs.yaml
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -2,8 +2,14 @@ table:
|
||||
name: provider_requests
|
||||
schema: auth
|
||||
configuration:
|
||||
column_config: {}
|
||||
custom_column_names: {}
|
||||
column_config:
|
||||
id:
|
||||
custom_name: id
|
||||
options:
|
||||
custom_name: options
|
||||
custom_column_names:
|
||||
id: id
|
||||
options: options
|
||||
custom_name: authProviderRequests
|
||||
custom_root_fields:
|
||||
delete: deleteAuthProviderRequests
|
||||
|
||||
@@ -2,8 +2,11 @@ table:
|
||||
name: providers
|
||||
schema: auth
|
||||
configuration:
|
||||
column_config: {}
|
||||
custom_column_names: {}
|
||||
column_config:
|
||||
id:
|
||||
custom_name: id
|
||||
custom_column_names:
|
||||
id: id
|
||||
custom_name: authProviders
|
||||
custom_root_fields:
|
||||
delete: deleteAuthProviders
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
table:
|
||||
name: refresh_token_types
|
||||
schema: auth
|
||||
is_enum: true
|
||||
configuration:
|
||||
column_config: {}
|
||||
custom_column_names: {}
|
||||
custom_name: authRefreshTokenTypes
|
||||
custom_root_fields:
|
||||
delete: deleteAuthRefreshTokenTypes
|
||||
delete_by_pk: deleteAuthRefreshTokenType
|
||||
insert: insertAuthRefreshTokenTypes
|
||||
insert_one: insertAuthRefreshTokenType
|
||||
select: authRefreshTokenTypes
|
||||
select_aggregate: authRefreshTokenTypesAggregate
|
||||
select_by_pk: authRefreshTokenType
|
||||
update: updateAuthRefreshTokenTypes
|
||||
update_by_pk: updateAuthRefreshTokenType
|
||||
array_relationships:
|
||||
- name: refreshTokens
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: type
|
||||
table:
|
||||
name: refresh_tokens
|
||||
schema: auth
|
||||
@@ -7,14 +7,14 @@ configuration:
|
||||
custom_name: createdAt
|
||||
expires_at:
|
||||
custom_name: expiresAt
|
||||
refresh_token:
|
||||
custom_name: refreshToken
|
||||
refresh_token_hash:
|
||||
custom_name: refreshTokenHash
|
||||
user_id:
|
||||
custom_name: userId
|
||||
custom_column_names:
|
||||
created_at: createdAt
|
||||
expires_at: expiresAt
|
||||
refresh_token: refreshToken
|
||||
refresh_token_hash: refreshTokenHash
|
||||
user_id: userId
|
||||
custom_name: authRefreshTokens
|
||||
custom_root_fields:
|
||||
@@ -31,3 +31,25 @@ object_relationships:
|
||||
- name: user
|
||||
using:
|
||||
foreign_key_constraint_on: user_id
|
||||
select_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- id
|
||||
- created_at
|
||||
- expires_at
|
||||
- metadata
|
||||
- type
|
||||
- user_id
|
||||
filter:
|
||||
user_id:
|
||||
_eq: X-Hasura-User-Id
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
filter:
|
||||
_and:
|
||||
- user_id:
|
||||
_eq: X-Hasura-User-Id
|
||||
- type:
|
||||
_eq: pat
|
||||
|
||||
@@ -2,8 +2,11 @@ table:
|
||||
name: roles
|
||||
schema: auth
|
||||
configuration:
|
||||
column_config: {}
|
||||
custom_column_names: {}
|
||||
column_config:
|
||||
role:
|
||||
custom_name: role
|
||||
custom_column_names:
|
||||
role: role
|
||||
custom_name: authRoles
|
||||
custom_root_fields:
|
||||
delete: deleteAuthRoles
|
||||
|
||||
@@ -7,6 +7,8 @@ configuration:
|
||||
custom_name: accessToken
|
||||
created_at:
|
||||
custom_name: createdAt
|
||||
id:
|
||||
custom_name: id
|
||||
provider_id:
|
||||
custom_name: providerId
|
||||
provider_user_id:
|
||||
@@ -20,6 +22,7 @@ configuration:
|
||||
custom_column_names:
|
||||
access_token: accessToken
|
||||
created_at: createdAt
|
||||
id: id
|
||||
provider_id: providerId
|
||||
provider_user_id: providerUserId
|
||||
refresh_token: refreshToken
|
||||
|
||||
@@ -5,10 +5,16 @@ configuration:
|
||||
column_config:
|
||||
created_at:
|
||||
custom_name: createdAt
|
||||
id:
|
||||
custom_name: id
|
||||
role:
|
||||
custom_name: role
|
||||
user_id:
|
||||
custom_name: userId
|
||||
custom_column_names:
|
||||
created_at: createdAt
|
||||
id: id
|
||||
role: role
|
||||
user_id: userId
|
||||
custom_name: authUserRoles
|
||||
custom_root_fields:
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
table:
|
||||
name: user_security_keys
|
||||
schema: auth
|
||||
configuration:
|
||||
column_config:
|
||||
credential_id:
|
||||
custom_name: credentialId
|
||||
credential_public_key:
|
||||
custom_name: credentialPublicKey
|
||||
id:
|
||||
custom_name: id
|
||||
user_id:
|
||||
custom_name: userId
|
||||
custom_column_names:
|
||||
credential_id: credentialId
|
||||
credential_public_key: credentialPublicKey
|
||||
id: id
|
||||
user_id: userId
|
||||
custom_name: authUserSecurityKeys
|
||||
custom_root_fields:
|
||||
delete: deleteAuthUserSecurityKeys
|
||||
delete_by_pk: deleteAuthUserSecurityKey
|
||||
insert: insertAuthUserSecurityKeys
|
||||
insert_one: insertAuthUserSecurityKey
|
||||
select: authUserSecurityKeys
|
||||
select_aggregate: authUserSecurityKeysAggregate
|
||||
select_by_pk: authUserSecurityKey
|
||||
update: updateAuthUserSecurityKeys
|
||||
update_by_pk: updateAuthUserSecurityKey
|
||||
object_relationships:
|
||||
- name: user
|
||||
using:
|
||||
foreign_key_constraint_on: user_id
|
||||
select_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- id
|
||||
- nickname
|
||||
- user_id
|
||||
filter:
|
||||
user_id:
|
||||
_eq: X-Hasura-User-Id
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
filter:
|
||||
user_id:
|
||||
_eq: x-hasura-auth-elevated
|
||||
@@ -11,14 +11,22 @@ configuration:
|
||||
custom_name: createdAt
|
||||
default_role:
|
||||
custom_name: defaultRole
|
||||
disabled:
|
||||
custom_name: disabled
|
||||
display_name:
|
||||
custom_name: displayName
|
||||
email:
|
||||
custom_name: email
|
||||
email_verified:
|
||||
custom_name: emailVerified
|
||||
id:
|
||||
custom_name: id
|
||||
is_anonymous:
|
||||
custom_name: isAnonymous
|
||||
last_seen:
|
||||
custom_name: lastSeen
|
||||
locale:
|
||||
custom_name: locale
|
||||
new_email:
|
||||
custom_name: newEmail
|
||||
otp_hash:
|
||||
@@ -33,6 +41,8 @@ configuration:
|
||||
custom_name: phoneNumber
|
||||
phone_number_verified:
|
||||
custom_name: phoneNumberVerified
|
||||
ticket:
|
||||
custom_name: ticket
|
||||
ticket_expires_at:
|
||||
custom_name: ticketExpiresAt
|
||||
totp_secret:
|
||||
@@ -46,10 +56,14 @@ configuration:
|
||||
avatar_url: avatarUrl
|
||||
created_at: createdAt
|
||||
default_role: defaultRole
|
||||
disabled: disabled
|
||||
display_name: displayName
|
||||
email: email
|
||||
email_verified: emailVerified
|
||||
id: id
|
||||
is_anonymous: isAnonymous
|
||||
last_seen: lastSeen
|
||||
locale: locale
|
||||
new_email: newEmail
|
||||
otp_hash: otpHash
|
||||
otp_hash_expires_at: otpHashExpiresAt
|
||||
@@ -57,6 +71,7 @@ configuration:
|
||||
password_hash: passwordHash
|
||||
phone_number: phoneNumber
|
||||
phone_number_verified: phoneNumberVerified
|
||||
ticket: ticket
|
||||
ticket_expires_at: ticketExpiresAt
|
||||
totp_secret: totpSecret
|
||||
updated_at: updatedAt
|
||||
@@ -77,13 +92,6 @@ object_relationships:
|
||||
using:
|
||||
foreign_key_constraint_on: default_role
|
||||
array_relationships:
|
||||
- name: authenticators
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: user_id
|
||||
table:
|
||||
name: user_authenticators
|
||||
schema: auth
|
||||
- name: refreshTokens
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
@@ -98,6 +106,13 @@ array_relationships:
|
||||
table:
|
||||
name: user_roles
|
||||
schema: auth
|
||||
- name: securityKeys
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
column: user_id
|
||||
table:
|
||||
name: user_security_keys
|
||||
schema: auth
|
||||
- name: userProviders
|
||||
using:
|
||||
foreign_key_constraint_on:
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
table:
|
||||
name: notes
|
||||
schema: public
|
||||
configuration:
|
||||
column_config: {}
|
||||
custom_column_names: {}
|
||||
custom_root_fields:
|
||||
delete: deleteNotes
|
||||
delete_by_pk: deleteNote
|
||||
insert: insertNotes
|
||||
insert_one: insertNote
|
||||
select: notes
|
||||
select_aggregate: notesAggregate
|
||||
select_by_pk: note
|
||||
update: updateNotes
|
||||
update_by_pk: updateNote
|
||||
object_relationships:
|
||||
- name: user
|
||||
using:
|
||||
foreign_key_constraint_on: user_id
|
||||
insert_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
check:
|
||||
user_id:
|
||||
_eq: x-hasura-auth-elevated
|
||||
set:
|
||||
user_id: x-hasura-User-Id
|
||||
columns:
|
||||
- content
|
||||
- id
|
||||
select_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- content
|
||||
- created_at
|
||||
- id
|
||||
- updated_at
|
||||
filter:
|
||||
user_id:
|
||||
_eq: X-Hasura-User-Id
|
||||
allow_aggregations: true
|
||||
update_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- content
|
||||
filter:
|
||||
user_id:
|
||||
_eq: x-hasura-auth-elevated
|
||||
check: {}
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
filter:
|
||||
user_id:
|
||||
_eq: x-hasura-auth-elevated
|
||||
@@ -0,0 +1,42 @@
|
||||
table:
|
||||
name: virus
|
||||
schema: storage
|
||||
configuration:
|
||||
column_config:
|
||||
created_at:
|
||||
custom_name: createdAt
|
||||
file_id:
|
||||
custom_name: fileId
|
||||
filename:
|
||||
custom_name: filename
|
||||
id:
|
||||
custom_name: id
|
||||
updated_at:
|
||||
custom_name: updatedAt
|
||||
user_session:
|
||||
custom_name: userSession
|
||||
virus:
|
||||
custom_name: virus
|
||||
custom_column_names:
|
||||
created_at: createdAt
|
||||
file_id: fileId
|
||||
filename: filename
|
||||
id: id
|
||||
updated_at: updatedAt
|
||||
user_session: userSession
|
||||
virus: virus
|
||||
custom_name: virus
|
||||
custom_root_fields:
|
||||
delete: deleteViruses
|
||||
delete_by_pk: deleteVirus
|
||||
insert: insertViruses
|
||||
insert_one: insertVirus
|
||||
select: viruses
|
||||
select_aggregate: virusesAggregate
|
||||
select_by_pk: virus
|
||||
update: updateViruses
|
||||
update_by_pk: updateVirus
|
||||
object_relationships:
|
||||
- name: file
|
||||
using:
|
||||
foreign_key_constraint_on: file_id
|
||||
@@ -1,11 +1,14 @@
|
||||
- "!include auth_provider_requests.yaml"
|
||||
- "!include auth_providers.yaml"
|
||||
- "!include auth_refresh_token_types.yaml"
|
||||
- "!include auth_refresh_tokens.yaml"
|
||||
- "!include auth_roles.yaml"
|
||||
- "!include auth_user_authenticators.yaml"
|
||||
- "!include auth_user_providers.yaml"
|
||||
- "!include auth_user_roles.yaml"
|
||||
- "!include auth_user_security_keys.yaml"
|
||||
- "!include auth_users.yaml"
|
||||
- "!include public_books.yaml"
|
||||
- "!include public_notes.yaml"
|
||||
- "!include storage_buckets.yaml"
|
||||
- "!include storage_files.yaml"
|
||||
- "!include storage_virus.yaml"
|
||||
|
||||
1
examples/vue-apollo/nhost/metadata/metrics_config.yaml
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
1
examples/vue-apollo/nhost/metadata/opentelemetry.yaml
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE "public"."notes";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE TABLE "public"."notes" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "content" text NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "user_id" uuid NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"));
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
@@ -1,7 +1,7 @@
|
||||
[global]
|
||||
|
||||
[hasura]
|
||||
version = 'v2.25.1-ce'
|
||||
version = 'v2.33.4-ce'
|
||||
adminSecret = '{{ secrets.HASURA_GRAPHQL_ADMIN_SECRET }}'
|
||||
webhookSecret = '{{ secrets.NHOST_WEBHOOK_SECRET }}'
|
||||
|
||||
@@ -28,10 +28,13 @@ httpPoolSize = 100
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
version = '0.26.0'
|
||||
|
||||
[auth.elevatedPrivileges]
|
||||
mode = 'required'
|
||||
|
||||
[auth.redirections]
|
||||
clientUrl = 'http://localhost:3000'
|
||||
clientUrl = 'http://localhost:5173'
|
||||
|
||||
[auth.signUp]
|
||||
enabled = true
|
||||
@@ -66,7 +69,7 @@ expiresIn = 43200
|
||||
enabled = false
|
||||
|
||||
[auth.method.emailPasswordless]
|
||||
enabled = false
|
||||
enabled = true
|
||||
|
||||
[auth.method.emailPassword]
|
||||
hibpEnabled = false
|
||||
@@ -124,7 +127,11 @@ enabled = false
|
||||
enabled = false
|
||||
|
||||
[auth.method.webauthn]
|
||||
enabled = false
|
||||
enabled = true
|
||||
|
||||
[auth.method.webauthn.relyingParty]
|
||||
name = 'apollo-example'
|
||||
origins = ['https://react-apollo.example.nhost.io']
|
||||
|
||||
[auth.method.webauthn.attestation]
|
||||
timeout = 60000
|
||||
@@ -133,12 +140,12 @@ timeout = 60000
|
||||
enabled = false
|
||||
|
||||
[postgres]
|
||||
version = '14.6-20230406-2'
|
||||
version = '14.6-20240129-1'
|
||||
|
||||
[provider]
|
||||
|
||||
[storage]
|
||||
version = '0.3.4'
|
||||
version = '0.6.0'
|
||||
|
||||
[observability]
|
||||
[observability.grafana]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/vue-apollo",
|
||||
"private": true,
|
||||
"version": "0.0.10",
|
||||
"version": "0.2.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
@@ -14,24 +14,24 @@
|
||||
"verify:fix": "run-p prettier:fix lint:fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.8.9",
|
||||
"@apollo/client": "^3.9.4",
|
||||
"@mdi/font": "5.9.55",
|
||||
"@nhost/apollo": "^6.0.2",
|
||||
"@nhost/nhost-js": "^3.0.2",
|
||||
"@nhost/vue": "^2.0.3",
|
||||
"@nhost/apollo": "workspace:^",
|
||||
"@nhost/nhost-js": "workspace:^",
|
||||
"@nhost/vue": "workspace:^",
|
||||
"@vue/apollo-composable": "4.0.0-alpha.18",
|
||||
"graphql": "16.8.1",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"vite-plugin-vuetify": "^1.0.2",
|
||||
"vue": "^3.4.11",
|
||||
"vue": "^3.4.15",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue3-dropzone": "^2.2.1",
|
||||
"vuetify": "3.0.0-beta.10",
|
||||
"webfontloader": "^1.6.28"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nhost/hasura-auth-js": "^2.1.11",
|
||||
"@nhost/hasura-auth-js": "workspace:^",
|
||||
"@types/webfontloader": "^1.6.38",
|
||||
"@vitejs/plugin-vue": "^4.6.2",
|
||||
"@xstate/inspect": "^0.6.5",
|
||||
|
||||
1259
examples/vue-apollo/pnpm-lock.yaml
generated
@@ -2,6 +2,12 @@
|
||||
<v-list nav>
|
||||
<v-list-item title="Home" to="/" value="home" prepend-icon="mdi-home" />
|
||||
<v-list-item title="Profile" to="/profile" value="profile" prepend-icon="mdi-account" />
|
||||
<v-list-item
|
||||
title="Secret Notes"
|
||||
to="/secret-notes"
|
||||
value="secret-notes"
|
||||
prepend-icon="mdi-lock"
|
||||
/>
|
||||
<v-list-item title="Apollo" to="/apollo" value="apollo" prepend-icon="mdi-api" />
|
||||
<v-list-item title="Storage" to="/storage" value="storage" prepend-icon="mdi-server" />
|
||||
<v-list-item title="About" to="/about" value="about" prepend-icon="mdi-information" />
|
||||
@@ -11,19 +17,68 @@
|
||||
prepend-icon="mdi-exit-to-app"
|
||||
@click="signOutHandler"
|
||||
/>
|
||||
|
||||
<v-card-text class="d-flex flex-column align-center justify-space-between align-self-end">
|
||||
<span>Elevated permissions: {{ elevated }}</span>
|
||||
<v-btn variant="text" color="primary" @click="handleElevate(user?.email)"> Elevate </v-btn>
|
||||
</v-card-text>
|
||||
</v-list>
|
||||
|
||||
<v-snackbar :modelValue="showElevateSuccess">
|
||||
You now have an elevated permission
|
||||
<template v-slot:actions>
|
||||
<v-btn color="indigo" variant="text" @click="showElevateError = false"> Close </v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
<v-snackbar :modelValue="showElevateError">
|
||||
Could not elevate permission
|
||||
<template v-slot:actions>
|
||||
<v-btn color="indigo" variant="text" @click="showElevateError = false"> Close </v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
<v-snackbar :modelValue="loggedOutWarning">
|
||||
You are logged out. Please login first!
|
||||
<template v-slot:actions>
|
||||
<v-btn color="indigo" variant="text" @click="loggedOutWarning = false"> Close </v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ref } from 'vue'
|
||||
import { useAuthenticated, useSignOut, useElevateSecurityKeyEmail, useUserData } from '@nhost/vue'
|
||||
|
||||
import { useAuthenticated, useSignOut } from '@nhost/vue'
|
||||
|
||||
const user = useUserData()
|
||||
const router = useRouter()
|
||||
const { signOut } = useSignOut()
|
||||
const showElevateError = ref(false)
|
||||
const showElevateSuccess = ref(false)
|
||||
const loggedOutWarning = ref(false)
|
||||
const authenticated = useAuthenticated()
|
||||
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
|
||||
|
||||
const signOutHandler = async () => {
|
||||
await signOut()
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
const handleElevate = async (email: string | undefined) => {
|
||||
if (!authenticated.value) {
|
||||
loggedOutWarning.value = true
|
||||
return
|
||||
}
|
||||
|
||||
if (email) {
|
||||
const { elevated, isError } = await elevateEmailSecurityKey(email)
|
||||
|
||||
if (elevated) {
|
||||
showElevateSuccess.value = true
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
showElevateError.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,228 @@
|
||||
<div className="d-flex align-center flex-column">
|
||||
<v-card width="400">
|
||||
<v-card-title>Profile page</v-card-title>
|
||||
<v-card-text> Here is the profile page </v-card-text>
|
||||
<v-card-text> {{ userEmail }} </v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-card width="400" class="mt-2 pa-4">
|
||||
<v-card-title>Add Security Key</v-card-title>
|
||||
|
||||
<form @submit="handleAddSecurityKey">
|
||||
<v-text-field v-model="nickname" label="NickName" />
|
||||
<v-btn
|
||||
block
|
||||
color="primary"
|
||||
class="my-1"
|
||||
type="submit"
|
||||
:disabled="isChangeEmailLoading"
|
||||
:loading="isChangeEmailLoading"
|
||||
>
|
||||
Add
|
||||
</v-btn>
|
||||
</form>
|
||||
|
||||
<v-list density="compact">
|
||||
<v-list-subheader>Security Keys</v-list-subheader>
|
||||
<v-list-item v-for="(key, i) in securityKeysList" :key="i" :value="key.id">
|
||||
<div className="d-flex align-center justify-space-between">
|
||||
<v-list-item-title>{{ key.id }}</v-list-item-title>
|
||||
<v-btn
|
||||
variant="flat"
|
||||
prepend-icon="mdi-delete"
|
||||
@click="handleRemoveSecurityKey(key.id)"
|
||||
/>
|
||||
</div>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
|
||||
<v-card width="400" class="mt-2 pa-4">
|
||||
<v-card-title>Change Email</v-card-title>
|
||||
|
||||
<form @submit="handleChangeEmail">
|
||||
<v-text-field v-model="email" label="Email" />
|
||||
<v-btn
|
||||
block
|
||||
color="primary"
|
||||
class="my-1"
|
||||
type="submit"
|
||||
:disabled="isChangeEmailLoading"
|
||||
:loading="isChangeEmailLoading"
|
||||
>
|
||||
Change email
|
||||
</v-btn>
|
||||
</form>
|
||||
</v-card>
|
||||
|
||||
<v-card width="400" class="mt-2 pa-4">
|
||||
<v-card-title>Change Password</v-card-title>
|
||||
|
||||
<form @submit="handleChangePassword">
|
||||
<v-text-field v-model="password" label="Password" type="password" />
|
||||
<v-btn
|
||||
block
|
||||
color="primary"
|
||||
class="my-1"
|
||||
type="submit"
|
||||
:disabled="isChangePasswordLoading"
|
||||
:loading="isChangePasswordLoading"
|
||||
>
|
||||
Change password
|
||||
</v-btn>
|
||||
</form>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<error-snack-bar :error="elevateError" />
|
||||
<error-snack-bar :error="changeEmailError" />
|
||||
<v-snackbar :modelValue="successSnackBar">OK</v-snackbar>
|
||||
|
||||
<error-snack-bar v-model="showElevatePermissionError"
|
||||
>Could not elevate permission</error-snack-bar
|
||||
>
|
||||
|
||||
<error-snack-bar v-model="showRemoveKeyError"></error-snack-bar>
|
||||
<v-snackbar v-model="showRemoveKeyError">
|
||||
Could not remove key
|
||||
<template #actions>
|
||||
<v-btn color="blue" variant="text" @click="showRemoveKeyError = false"> Close </v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
|
||||
<verification-email-dialog v-model="emailVerificationDialog" :email="email" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { gql } from '@apollo/client/core'
|
||||
import {
|
||||
useChangeEmail,
|
||||
useChangePassword,
|
||||
useElevateSecurityKeyEmail,
|
||||
useAddSecurityKey,
|
||||
useUserEmail,
|
||||
useUserId
|
||||
} from '@nhost/vue'
|
||||
import { useMutation, useQuery } from '@vue/apollo-composable'
|
||||
import { computed } from 'vue'
|
||||
import { ref, unref } from 'vue'
|
||||
|
||||
const email = ref('')
|
||||
const password = ref('')
|
||||
const nickname = ref('')
|
||||
const successSnackBar = ref(false)
|
||||
|
||||
const userId = useUserId()
|
||||
const userEmail = useUserEmail()
|
||||
const emailVerificationDialog = ref(false)
|
||||
const showElevatePermissionError = ref(false)
|
||||
const showRemoveKeyError = ref(false)
|
||||
const addSecurityKeyError = ref(false)
|
||||
const elevateError = ref(null)
|
||||
const changeEmailError = ref(null)
|
||||
const { changeEmail, isLoading: isChangeEmailLoading } = useChangeEmail()
|
||||
const { changePassword, isLoading: isChangePasswordLoading } = useChangePassword()
|
||||
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
|
||||
const { add } = useAddSecurityKey()
|
||||
|
||||
const SECURITY_KEYS_LIST = gql`
|
||||
query securityKeys($userId: uuid!) {
|
||||
authUserSecurityKeys(where: { userId: { _eq: $userId } }) {
|
||||
id
|
||||
nickname
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const REMOVE_SECURITY_KEY = gql`
|
||||
mutation removeSecurityKey($id: uuid!) {
|
||||
deleteAuthUserSecurityKey(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const { result: securityKeys, refetch } = useQuery(SECURITY_KEYS_LIST, { userId }, {})
|
||||
const { mutate: removeKey } = useMutation(REMOVE_SECURITY_KEY)
|
||||
|
||||
const securityKeysList = computed(() => securityKeys.value?.authUserSecurityKeys || [])
|
||||
|
||||
const checkElevatedPermission = async () => {
|
||||
let elevatedValue = unref(elevated)
|
||||
|
||||
if (!elevatedValue && securityKeys.value.authUserSecurityKeys.length > 0) {
|
||||
const { elevated } = await elevateEmailSecurityKey(userEmail.value as string)
|
||||
|
||||
if (!elevated) {
|
||||
throw new Error('Permissions were not elevated')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleChangeEmail = async (e: Event) => {
|
||||
e.preventDefault()
|
||||
|
||||
try {
|
||||
await checkElevatedPermission()
|
||||
} catch (error) {
|
||||
showElevatePermissionError.value = true
|
||||
}
|
||||
|
||||
const { needsEmailVerification } = await changeEmail(email)
|
||||
|
||||
if (needsEmailVerification) {
|
||||
emailVerificationDialog.value = true
|
||||
} else {
|
||||
successSnackBar.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const handleChangePassword = async (e: Event) => {
|
||||
e.preventDefault()
|
||||
|
||||
try {
|
||||
await checkElevatedPermission()
|
||||
} catch (error) {
|
||||
showElevatePermissionError.value = true
|
||||
}
|
||||
|
||||
const { error: changePasswordError } = await changePassword(password)
|
||||
|
||||
if (!changePasswordError) {
|
||||
successSnackBar.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddSecurityKey = async (e: Event) => {
|
||||
e.preventDefault()
|
||||
|
||||
try {
|
||||
await checkElevatedPermission()
|
||||
} catch (error) {
|
||||
showElevatePermissionError.value = true
|
||||
}
|
||||
|
||||
const { isError } = await add(nickname.value)
|
||||
|
||||
if (isError) {
|
||||
addSecurityKeyError.value = true
|
||||
} else {
|
||||
nickname.value = ''
|
||||
refetch()
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemoveSecurityKey = async (id: string) => {
|
||||
try {
|
||||
await checkElevatedPermission()
|
||||
} catch (error) {
|
||||
showElevatePermissionError.value = true
|
||||
}
|
||||
|
||||
try {
|
||||
await removeKey({ id })
|
||||
await refetch()
|
||||
} catch (error) {
|
||||
showRemoveKeyError.value = true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
127
examples/vue-apollo/src/pages/SecretNotesPage.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div className="d-flex align-center flex-column">
|
||||
<v-card width="400" tile>
|
||||
<v-card-title>Secret Notes</v-card-title>
|
||||
<v-col>
|
||||
<v-row class="px-4 mb-2 align-center">
|
||||
<v-text-field v-model="content" label="Note" class="mt-4 mr-2" />
|
||||
<v-btn size="large" @click="addNote">Add</v-btn>
|
||||
</v-row>
|
||||
<v-list density="compact" v-if="result">
|
||||
<v-list-subheader>Notes</v-list-subheader>
|
||||
<v-list-item v-for="(note, i) in result.notes" :key="i" :value="note.id">
|
||||
<div className="d-flex align-center justify-space-between">
|
||||
<v-list-item-title v-text="note.content"></v-list-item-title>
|
||||
<v-btn variant="flat" prepend-icon="mdi-delete" @click="deleteNote(note.id)" />
|
||||
</div>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-col>
|
||||
</v-card>
|
||||
</div>
|
||||
<error-snack-bar :error="insertNoteError" />
|
||||
<error-snack-bar :error="deleteNoteError" />
|
||||
<error-snack-bar v-model="showElevatePermissionError"
|
||||
>Could not elevate permission</error-snack-bar
|
||||
>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, unref } from 'vue'
|
||||
|
||||
import { gql } from '@apollo/client/core'
|
||||
import { useAuthenticated, useElevateSecurityKeyEmail, useUserEmail, useUserId } from '@nhost/vue'
|
||||
import { useQuery, useMutation } from '@vue/apollo-composable'
|
||||
|
||||
const content = ref('')
|
||||
const userId = useUserId()
|
||||
const userEmail = useUserEmail()
|
||||
const isAuthenticated = useAuthenticated()
|
||||
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
|
||||
const showElevatePermissionError = ref(false)
|
||||
|
||||
const GET_NOTES = gql`
|
||||
query notesList {
|
||||
notes {
|
||||
id
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const INSERT_NOTE = gql`
|
||||
mutation insertNote($content: String!) {
|
||||
insertNote(object: { content: $content }) {
|
||||
id
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const DELETE_NOTE = gql`
|
||||
mutation deleteNote($noteId: uuid!) {
|
||||
deleteNote(id: $noteId) {
|
||||
id
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const SECURITY_KEYS_LIST = gql`
|
||||
query securityKeys($userId: uuid!) {
|
||||
authUserSecurityKeys(where: { userId: { _eq: $userId } }) {
|
||||
id
|
||||
nickname
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const { result } = useQuery(
|
||||
GET_NOTES,
|
||||
null,
|
||||
computed(() => ({
|
||||
enabled: isAuthenticated.value
|
||||
}))
|
||||
)
|
||||
|
||||
const { result: securityKeys } = useQuery(SECURITY_KEYS_LIST, { userId })
|
||||
|
||||
const { mutate: insertNoteMutation, error: insertNoteError } = useMutation(INSERT_NOTE)
|
||||
const { mutate: deleteNoteMutation, error: deleteNoteError } = useMutation(DELETE_NOTE)
|
||||
|
||||
const checkElevatedPermission = async () => {
|
||||
let elevatedValue = unref(elevated)
|
||||
|
||||
if (!elevatedValue && securityKeys.value.authUserSecurityKeys.length > 0) {
|
||||
const { elevated } = await elevateEmailSecurityKey(userEmail.value as string)
|
||||
|
||||
if (!elevated) {
|
||||
throw new Error('Permissions were not elevated')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const addNote = async () => {
|
||||
try {
|
||||
await checkElevatedPermission()
|
||||
} catch (error) {
|
||||
showElevatePermissionError.value = true
|
||||
}
|
||||
|
||||
await insertNoteMutation({ content: content.value }, { refetchQueries: ['notesList'] })
|
||||
|
||||
content.value = ''
|
||||
}
|
||||
|
||||
const deleteNote = async (noteId: string) => {
|
||||
try {
|
||||
await checkElevatedPermission()
|
||||
} catch (error) {
|
||||
showElevatePermissionError.value = true
|
||||
}
|
||||
|
||||
await deleteNoteMutation({ noteId: noteId }, { refetchQueries: ['notesList'] })
|
||||
|
||||
content.value = ''
|
||||
}
|
||||
</script>
|
||||