Compare commits

...

20 Commits

Author SHA1 Message Date
github-actions[bot]
f33e07b191 chore: update versions (#2538)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/hasura-auth-js@2.3.0

### Minor Changes

-   017f1a6: feat: add elevated permission examples

## @nhost/react@3.2.0

### Minor Changes

-   017f1a6: feat: add elevated permission examples

### Patch Changes

-   @nhost/nhost-js@3.0.5

## @nhost/vue@2.2.0

### Minor Changes

-   017f1a6: feat: add elevated permission examples

### Patch Changes

-   @nhost/nhost-js@3.0.5

## @nhost/apollo@6.0.5

### Patch Changes

-   @nhost/nhost-js@3.0.5

## @nhost/react-apollo@9.0.0

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0
    -   @nhost/apollo@6.0.5

## @nhost/react-urql@6.0.0

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0

## @nhost/nextjs@2.1.2

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0

## @nhost/nhost-js@3.0.5

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/hasura-auth-js@2.3.0

## @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

## @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

## @nhost/dashboard@1.6.9

### Patch Changes

-   @nhost/react-apollo@9.0.0
-   @nhost/nextjs@2.1.2

## @nhost-examples/cli@0.1.6

### Patch Changes

-   @nhost/nhost-js@3.0.5

## @nhost-examples/codegen-react-apollo@0.1.14

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0
    -   @nhost/react-apollo@9.0.0

## @nhost-examples/codegen-react-query@0.1.15

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0

## @nhost-examples/codegen-react-urql@0.0.11

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0
    -   @nhost/react-urql@6.0.0

## @nhost-examples/multi-tenant-one-to-many@2.0.4

### Patch Changes

-   @nhost/nhost-js@3.0.5

## @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

## @nhost-examples/node-storage@0.0.8

### Patch Changes

-   @nhost/nhost-js@3.0.5

## @nhost-examples/nextjs-server-components@0.2.2

### Patch Changes

-   @nhost/nhost-js@3.0.5

## @nhost-examples/react-gqty@1.0.4

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/react@3.2.0

## @nhost-examples/vue-quickstart@0.0.13

### Patch Changes

-   Updated dependencies [017f1a6]
    -   @nhost/vue@2.2.0
    -   @nhost/apollo@6.0.5

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-14 15:12:34 +01:00
Hassan Ben Jobrane
017f1a6c7b feat: add elevate workflow to react-apollo and vue-apollo example projects (#2521)
part-2 of https://github.com/nhost/nhost/issues/2394

---------

Co-authored-by: David Barroso <dbarrosop@dravetech.com>
2024-02-14 14:52:43 +01:00
github-actions[bot]
93957c8af3 chore: update versions (#2537)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/hasura-storage-js@2.4.0

### Minor Changes

-   2505b2e: fix: fix headers sent with getPresignedUrl

## @nhost/apollo@6.0.4

### Patch Changes

-   @nhost/nhost-js@3.0.4

## @nhost/react-apollo@8.0.1

### Patch Changes

-   @nhost/apollo@6.0.4
-   @nhost/react@3.1.1

## @nhost/react-urql@5.0.1

### Patch Changes

-   @nhost/react@3.1.1

## @nhost/nextjs@2.1.1

### Patch Changes

-   @nhost/react@3.1.1

## @nhost/nhost-js@3.0.4

### Patch Changes

-   Updated dependencies [2505b2e]
    -   @nhost/hasura-storage-js@2.4.0

## @nhost/react@3.1.1

### Patch Changes

-   @nhost/nhost-js@3.0.4

## @nhost/vue@2.1.1

### Patch Changes

-   @nhost/nhost-js@3.0.4

## @nhost/dashboard@1.6.8

### Patch Changes

-   @nhost/react-apollo@8.0.1
-   @nhost/nextjs@2.1.1

## @nhost-examples/cli@0.1.5

### Patch Changes

-   @nhost/nhost-js@3.0.4

## @nhost-examples/codegen-react-apollo@0.1.13

### Patch Changes

-   @nhost/react@3.1.1
-   @nhost/react-apollo@8.0.1

## @nhost-examples/codegen-react-query@0.1.14

### Patch Changes

-   @nhost/react@3.1.1

## @nhost-examples/codegen-react-urql@0.0.10

### Patch Changes

-   @nhost/react@3.1.1
-   @nhost/react-urql@5.0.1

## @nhost-examples/multi-tenant-one-to-many@2.0.3

### Patch Changes

-   @nhost/nhost-js@3.0.4

## @nhost-examples/nextjs@0.1.15

### Patch Changes

-   @nhost/react@3.1.1
-   @nhost/react-apollo@8.0.1
-   @nhost/nextjs@2.1.1

## @nhost-examples/node-storage@0.0.7

### Patch Changes

-   @nhost/nhost-js@3.0.4

## @nhost-examples/nextjs-server-components@0.2.1

### Patch Changes

-   @nhost/nhost-js@3.0.4

## @nhost-examples/react-apollo@0.2.1

### Patch Changes

-   @nhost/react@3.1.1
-   @nhost/react-apollo@8.0.1

## @nhost-examples/react-gqty@1.0.3

### Patch Changes

-   @nhost/react@3.1.1

## @nhost-examples/vue-apollo@0.1.1

### Patch Changes

-   @nhost/nhost-js@3.0.4
-   @nhost/apollo@6.0.4
-   @nhost/vue@2.1.1

## @nhost-examples/vue-quickstart@0.0.12

### Patch Changes

-   @nhost/apollo@6.0.4
-   @nhost/vue@2.1.1

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-14 10:29:57 +01:00
Nuno Pato
2505b2e26b fix: fix headers sent with getPresignedUrl (#2535) 2024-02-13 23:33:09 -01:00
Nuno Pato
5f4b4d2acc chore: update dependencies (#2536)
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-02-13 20:29:47 -01:00
github-actions[bot]
71a8ce4446 chore: update versions (#2524)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/docs@2.4.0

### Minor Changes

-   791b729: fix: remove auth method

## @nhost/dashboard@1.6.7

### Patch Changes

-   5ef5189: fix: update `@apollo/client` to `3.9.4` to fix a cache bug

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-12 16:33:16 +01:00
Hassan Ben Jobrane
5ef5189898 fix(dashboard): resolve change plan modal cache issue (#2532)
related to https://github.com/nhost/nhost/issues/2530
2024-02-12 16:08:22 +01:00
Nuno Pato
791b7295fb fix: docs: remove auth method (#2475)
- https://github.com/nhost/nhost/issues/2474
2024-02-08 10:36:51 -01:00
github-actions[bot]
25bc4b7fd6 chore: update versions (#2501)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/hasura-auth-js@2.2.0

### Minor Changes

- 1a61c65: feat: add 'elevateEmailSecurityKey' to the SDKs along with
integration into react-apollo and vue-apollo examples

## @nhost/hasura-storage-js@2.3.0

### Minor Changes

-   d3d1424: feat: Add support for authenticated download of files

### Patch Changes

-   e5bab6a: chore: update dependencies

## @nhost/nextjs@2.1.0

### Minor Changes

-   b19ffed: chore: update peerDependency to support nextjs 14

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/react@3.1.0

## @nhost/react@3.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
    -   @nhost/nhost-js@3.0.3

## @nhost/vue@2.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
    -   @nhost/nhost-js@3.0.3

## @nhost/apollo@6.0.3

### Patch Changes

-   e5bab6a: chore: update dependencies
    -   @nhost/nhost-js@3.0.3

## @nhost/google-translation@0.0.8

### Patch Changes

-   e5bab6a: chore: update dependencies

## @nhost/react-apollo@8.0.0

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/react@3.1.0
    -   @nhost/apollo@6.0.3

## @nhost/react-urql@5.0.0

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/react@3.1.0

## @nhost/stripe-graphql-js@1.0.7

### Patch Changes

-   e5bab6a: chore: update dependencies

## @nhost/nhost-js@3.0.3

### Patch Changes

-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
-   Updated dependencies [d3d1424]
    -   @nhost/hasura-auth-js@2.2.0
    -   @nhost/hasura-storage-js@2.3.0
    -   @nhost/graphql-js@0.1.5

## @nhost/docs@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

## @nhost-examples/nextjs-server-components@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

## @nhost-examples/react-apollo@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

## @nhost-examples/vue-apollo@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

## @nhost/dashboard@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

## @nhost-examples/cli@0.1.4

### Patch Changes

-   e5bab6a: chore: update dependencies
    -   @nhost/nhost-js@3.0.3

## @nhost-examples/codegen-react-apollo@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

## @nhost-examples/codegen-react-query@0.1.13

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/react@3.1.0

## @nhost-examples/codegen-react-urql@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

## @nhost-examples/multi-tenant-one-to-many@2.0.2

### Patch Changes

-   e5bab6a: chore: update dependencies
    -   @nhost/nhost-js@3.0.3

## @nhost-examples/nextjs@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

## @nhost-examples/node-storage@0.0.6

### Patch Changes

-   e5bab6a: chore: update dependencies
    -   @nhost/nhost-js@3.0.3

## @nhost-examples/sveltekit@0.2.3

### Patch Changes

-   e5bab6a: chore: update dependencies

## @nhost-examples/react-gqty@1.0.2

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/react@3.1.0

## @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

## @nhost-examples/vue-quickstart@0.0.11

### Patch Changes

-   e5bab6a: chore: update dependencies
-   Updated dependencies [1a61c65]
-   Updated dependencies [e5bab6a]
    -   @nhost/vue@2.1.0
    -   @nhost/apollo@6.0.3

## @nhost/docgen@0.1.13

### Patch Changes

-   e5bab6a: chore: update dependencies

## @nhost/sync-versions@0.0.10

### Patch Changes

-   e5bab6a: chore: update dependencies

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-02-06 14:41:47 +01:00
Hassan Ben Jobrane
da20159ec5 chore(examples): update auth version to 0.25.0 in nhost.toml (#2518) 2024-02-06 14:09:30 +01:00
David Barroso
2ae5ea8bc1 fix (docs): indicate that custom domains for postgres doesn't require configuration (#2506) 2024-02-06 11:54:18 +01:00
David Barroso
3ba485e582 fix (dashboard): added discord.com to connect-src (#2516) 2024-02-06 11:29:27 +01:00
David Barroso
e5bab6a061 chore: update dependencies (#2505)
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-02-06 10:25:48 +01:00
David Barroso
be64353145 Create SECURITY.md (#2515) 2024-02-06 10:00:38 +01:00
Hassan Ben Jobrane
2f5913c78d fix(examples): revert back auth version to 0.24.1 (#2512) 2024-02-05 17:18:52 +01:00
Nuno Pato
757ddd901c chore: docs: use hyphen instead of underscore (#2511) 2024-02-05 13:46:13 -01:00
Hassan Ben Jobrane
1a61c658a7 feat: add elevate func to the SDKs and the react-apollo example project (#2500)
part-1 of https://github.com/nhost/nhost/issues/2394
2024-02-05 13:25:14 +01:00
Nuno Pato
d3d14245c7 feat: hasura-storage-js: add authenticated download of files (#2507) 2024-02-05 11:24:34 -01:00
Alexander Mart
53d2f9d3e0 doc: fix method name in example code at refresh-session.mdx doc page (#2468)
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2024-02-03 15:54:08 +01:00
Hassan Ben Jobrane
8c34c69e79 chore: update nodemailer (#2510) 2024-02-02 20:31:29 +01:00
156 changed files with 6551 additions and 4548 deletions

View File

@@ -1,5 +0,0 @@
---
'@nhost/nextjs': minor
---
chore: update peerDependency to support nextjs 14

View 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
View 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.

View File

@@ -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

View File

@@ -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 [

View File

@@ -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": {

View File

@@ -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}

View File

@@ -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

View File

@@ -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`:
![nhost up](/images/guides/ai/local_development/nhost_up.png)
![nhost up](/images/guides/ai/local-development/nhost_up.png)
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:
![git status](/images/guides/ai/local_development/git_status.png)
![git status](/images/guides/ai/local-development/git_status.png)
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:
![migration](/images/guides/ai/local_development/migration.png)
![migration](/images/guides/ai/local-development/migration.png)
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:
![git status](/images/guides/ai/local_development/git_status_functions.png)
![git status](/images/guides/ai/local-development/git_status_functions.png)
Pushing them to your deployment branch will also deploy them to your cloud project.

View File

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

View File

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

View File

Before

Width:  |  Height:  |  Size: 916 KiB

After

Width:  |  Height:  |  Size: 916 KiB

View File

Before

Width:  |  Height:  |  Size: 393 KiB

After

Width:  |  Height:  |  Size: 393 KiB

View File

@@ -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"
]
},
{

View File

@@ -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"
}
}

View File

@@ -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'

View File

@@ -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

View File

@@ -1,4 +0,0 @@
---
title: createFileUploadMachine()
sidebarTitle: createFileUploadMachine()
---

View File

@@ -1,4 +0,0 @@
---
title: createMultipleFilesUploadMachine()
sidebarTitle: createMultipleFilesUploadMachine()
---

View File

@@ -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) &amp; Partial&lt;StorageUploadFileParams&gt;</code>
---
**<span className="parameter-name">interpreter</span>** <span className="optional-status">required</span> <code>ActorRefWithDeprecatedState&lt;FileUploadContext, &#123; type: "ADD", file: File, id: string, bucketId: string, name: string &#125; &#124; &#123; type: "UPLOAD", file: File, id: string, name: string, bucketId: string &#125; &amp; FileUploadConfig &#124; &#123; type: "UPLOAD_PROGRESS", progress: number, loaded: number, additions: number &#125; &#124; &#123; type: "UPLOAD_DONE", id: string, bucketId: string &#125; &#124; &#123; type: "UPLOAD_ERROR", error: StorageErrorPayload &#125; &#124; &#123; type: "CANCEL" &#125; &#124; &#123; type: "DESTROY" &#125;, &#123; value: any, context: FileUploadContext &#125;, ResolveTypegenMeta&lt;Typegen0, &#123; type: "ADD", file: File, id: string, bucketId: string, name: string &#125; &#124; &#123; type: "UPLOAD", file: File, id: string, name: string, bucketId: string &#125; &amp; FileUploadConfig &#124; &#123; type: "UPLOAD_PROGRESS", progress: number, loaded: number, additions: number &#125; &#124; &#123; type: "UPLOAD_DONE", id: string, bucketId: string &#125; &#124; &#123; type: "UPLOAD_ERROR", error: StorageErrorPayload &#125; &#124; &#123; type: "CANCEL" &#125; &#124; &#123; type: "DESTROY" &#125;, BaseActionObject, ServiceMap&gt;&gt; &#124; Interpreter&lt;FileUploadContext, any, &#123; type: "ADD", file: File, id: string, bucketId: string, name: string &#125; &#124; &#123; type: "UPLOAD", file: File, id: string, name: string, bucketId: string &#125; &amp; FileUploadConfig &#124; &#123; type: "UPLOAD_PROGRESS", progress: number, loaded: number, additions: number &#125; &#124; &#123; type: "UPLOAD_DONE", id: string, bucketId: string &#125; &#124; &#123; type: "UPLOAD_ERROR", error: StorageErrorPayload &#125; &#124; &#123; type: "CANCEL" &#125; &#124; &#123; type: "DESTROY" &#125;, &#123; value: any, context: FileUploadContext &#125;, ResolveTypegenMeta&lt;Typegen0, &#123; type: "ADD", file: File, id: string, bucketId: string, name: string &#125; &#124; &#123; type: "UPLOAD", file: File, id: string, name: string, bucketId: string &#125; &amp; FileUploadConfig &#124; &#123; type: "UPLOAD_PROGRESS", progress: number, loaded: number, additions: number &#125; &#124; &#123; type: "UPLOAD_DONE", id: string, bucketId: string &#125; &#124; &#123; type: "UPLOAD_ERROR", error: StorageErrorPayload &#125; &#124; &#123; type: "CANCEL" &#125; &#124; &#123; type: "DESTROY" &#125;, BaseActionObject, ServiceMap&gt;&gt;</code>
---

View File

@@ -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) &amp; [`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&lt;MultipleFilesUploadContext, any, &#123; type: "ADD", files: AnyFileList, bucketId: string &#125; &#124; &#123; type: "UPLOAD", files: AnyFileList, bucketId: string &#125; &amp; FileUploadConfig &#124; &#123; type: "UPLOAD_PROGRESS", additions: number &#125; &#124; &#123; type: "UPLOAD_DONE" &#125; &#124; &#123; type: "UPLOAD_ERROR" &#125; &#124; &#123; type: "CANCEL" &#125; &#124; &#123; type: "REMOVE" &#125; &#124; &#123; type: "CLEAR" &#125;, &#123; value: any, context: MultipleFilesUploadContext &#125;, ResolveTypegenMeta&lt;Typegen0, &#123; type: "ADD", files: AnyFileList, bucketId: string &#125; &#124; &#123; type: "UPLOAD", files: AnyFileList, bucketId: string &#125; &amp; FileUploadConfig &#124; &#123; type: "UPLOAD_PROGRESS", additions: number &#125; &#124; &#123; type: "UPLOAD_DONE" &#125; &#124; &#123; type: "UPLOAD_ERROR" &#125; &#124; &#123; type: "CANCEL" &#125; &#124; &#123; type: "REMOVE" &#125; &#124; &#123; type: "CLEAR" &#125;, BaseActionObject, ServiceMap&gt;&gt;</code>
---

View 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&lt;string, string&gt;</code> | | Optional headers to be sent with the request |
---

View File

@@ -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&lt;string, string&gt;</code>
Optional headers to be sent with the request
---

View File

@@ -0,0 +1,13 @@
---
title: StorageDownloadFileResponse
sidebarTitle: StorageDownloadFileResponse
description: No description provided.
---
# `StorageDownloadFileResponse`
```ts
type StorageDownloadFileResponse =
| { file: Blob; error: null }
| { file: null; error: Error }
```

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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:^"
}
}

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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"
}
}

View File

@@ -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

View File

@@ -1,5 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverActions: true
}
}
module.exports = nextConfig

View File

@@ -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"
}
}

View File

@@ -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,

View File

@@ -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>
);
}
)
}

View File

@@ -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"

View File

@@ -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:

View File

@@ -146,7 +146,7 @@ version = '14.6-20230406-2'
[provider]
[storage]
version = '0.4.0'
version = '0.5.1'
[observability]
[observability.grafana]

View File

@@ -1,5 +1,10 @@
---
## 0.2.3
### Patch Changes
- e5bab6a: chore: update dependencies
## 0.2.2
### Patch Changes

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -46,4 +46,4 @@ delete_permissions:
permission:
filter:
user_id:
_eq: X-Hasura-User-Id
_eq: x-hasura-auth-elevated

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -0,0 +1 @@
DROP TABLE "public"."notes";

View File

@@ -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;

View File

@@ -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]

View File

@@ -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",

View File

@@ -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:

View File

@@ -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={

View File

@@ -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>
)
}

View File

@@ -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} />
}

View 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>
)
}

View File

@@ -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'];
}>;

View File

@@ -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>

View File

@@ -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({

View File

@@ -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>

View File

@@ -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>

View File

@@ -0,0 +1 @@
export * from './security-keys'

View 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
}
}
`

View File

@@ -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()]
})

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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"
}

View File

@@ -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

View File

@@ -0,0 +1 @@
{}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
DROP TABLE "public"."notes";

View File

@@ -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;

View File

@@ -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]

View File

@@ -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",

File diff suppressed because it is too large Load Diff

View File

@@ -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>

View File

@@ -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>

View 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>

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