Compare commits
96 Commits
@nhost/das
...
@nhost/das
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4341c3706 | ||
|
|
c2ef17c0a0 | ||
|
|
1045ea0a46 | ||
|
|
5faaf36e26 | ||
|
|
65b6a48d51 | ||
|
|
23e18fb734 | ||
|
|
44be6dc0a5 | ||
|
|
3c35948986 | ||
|
|
7883bbcbd1 | ||
|
|
42fddcf790 | ||
|
|
5d1a444451 | ||
|
|
98d17a3066 | ||
|
|
f70f36be08 | ||
|
|
50fe08624f | ||
|
|
6cb7dd8203 | ||
|
|
f859159ef5 | ||
|
|
32c246b7a9 | ||
|
|
f004fd067a | ||
|
|
82340b5d54 | ||
|
|
8fff3e06bd | ||
|
|
527a661222 | ||
|
|
172fd8dfed | ||
|
|
a99ca90279 | ||
|
|
5892fd7f01 | ||
|
|
0344cc9a6d | ||
|
|
2697e28cf2 | ||
|
|
54231b119f | ||
|
|
7c977e7143 | ||
|
|
7c3019389e | ||
|
|
46f028b9fd | ||
|
|
683e85b89f | ||
|
|
b0f27c908d | ||
|
|
5f2618e183 | ||
|
|
29037147f2 | ||
|
|
95b630a621 | ||
|
|
0fdfd8ad81 | ||
|
|
174b4165b3 | ||
|
|
04257bc09c | ||
|
|
184c341f05 | ||
|
|
52fdce291f | ||
|
|
c43ff40e1f | ||
|
|
4ec2f8f186 | ||
|
|
7ece80a39e | ||
|
|
1327351e1b | ||
|
|
1fbdf630a5 | ||
|
|
93f573ea98 | ||
|
|
ac78629414 | ||
|
|
3403744c22 | ||
|
|
cef11677f4 | ||
|
|
ddadf3399c | ||
|
|
c768341ce8 | ||
|
|
1396cbe4c0 | ||
|
|
76761b4970 | ||
|
|
af33c21d10 | ||
|
|
1b01d56e82 | ||
|
|
229acb1d60 | ||
|
|
0bc9a41e51 | ||
|
|
b4dccd4496 | ||
|
|
31683fa926 | ||
|
|
7107089a29 | ||
|
|
e9d5d0a53e | ||
|
|
4e0b132d20 | ||
|
|
425476759f | ||
|
|
04784d880b | ||
|
|
130131c488 | ||
|
|
a6c7300e14 | ||
|
|
1a84610b74 | ||
|
|
6c43529eff | ||
|
|
63309cbcd6 | ||
|
|
998b1d5963 | ||
|
|
42d2a89de3 | ||
|
|
731f094cf8 | ||
|
|
3454605582 | ||
|
|
e4479afab4 | ||
|
|
6edae34bf0 | ||
|
|
80b6464f60 | ||
|
|
e3880dbe8a | ||
|
|
ea991228e2 | ||
|
|
7cb568be52 | ||
|
|
dacaa7cad7 | ||
|
|
30a688778e | ||
|
|
d4f79c05b4 | ||
|
|
e10d313e37 | ||
|
|
77e8fb471c | ||
|
|
f40a3f23ac | ||
|
|
17dea7e60b | ||
|
|
23527fc388 | ||
|
|
8ec6b85bac | ||
|
|
b067838984 | ||
|
|
7553506e18 | ||
|
|
e58a9f1aaa | ||
|
|
d4bfea963f | ||
|
|
88779ad950 | ||
|
|
90929e9357 | ||
|
|
2f4d5814ed | ||
|
|
6d08b34309 |
22
.github/CODEOWNERS
vendored
22
.github/CODEOWNERS
vendored
@@ -1,14 +1,14 @@
|
||||
# Documentation
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
/packages @szilarddoro
|
||||
/packages/docgen @szilarddoro
|
||||
/integrations/stripe-graphql-js @elitan
|
||||
/.github @szilarddoro
|
||||
/dashboard/ @szilarddoro
|
||||
/docs/ @elitan
|
||||
/config/ @szilarddoro
|
||||
/examples/ @szilarddoro
|
||||
/examples/codegen-react-apollo @elitan @szilarddoro
|
||||
/examples/codegen-react-query @elitan @szilarddoro
|
||||
/examples/react-apollo-crm @elitan @szilarddoro
|
||||
/packages @nunopato @onehassan
|
||||
/packages/docgen @nunopato @onehassan
|
||||
/integrations/stripe-graphql-js @nunopato @onehassan
|
||||
/.github @nunopato @onehassan
|
||||
/dashboard/ @nunopato @onehassan
|
||||
/docs/ @nunopato @onehassan
|
||||
/config/ @nunopato @onehassan
|
||||
/examples/ @nunopato @onehassan
|
||||
/examples/codegen-react-apollo @nunopato @onehassan
|
||||
/examples/codegen-react-query @nunopato @onehassan
|
||||
/examples/react-apollo-crm @nunopato @onehassan
|
||||
|
||||
1
.github/labeler.yml
vendored
1
.github/labeler.yml
vendored
@@ -4,7 +4,6 @@ dashboard:
|
||||
documentation:
|
||||
- any:
|
||||
- docs/**/*
|
||||
- '!docs/docs/reference/docgen/**/*'
|
||||
|
||||
examples:
|
||||
- examples/**/*
|
||||
|
||||
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -146,7 +146,7 @@ jobs:
|
||||
run: echo "NHOST_TEST_DASHBOARD_URL=https://${{ steps.fetch-dashboard-preview-url.outputs.preview_url }}" >> $GITHUB_ENV
|
||||
# * Run the `ci` script of the current package of the matrix. Dependencies build is cached by Turborepo
|
||||
- name: Run e2e tests
|
||||
timeout-minutes: 15
|
||||
timeout-minutes: 20
|
||||
run: pnpm --filter="${{ matrix.package.name }}" run e2e
|
||||
- id: file-name
|
||||
if: ${{ failure() }}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -23,7 +23,7 @@ node_modules/
|
||||
tmp/
|
||||
.pnpm-store
|
||||
.turbo
|
||||
.env
|
||||
.env*
|
||||
.secrets
|
||||
out/
|
||||
|
||||
|
||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
},
|
||||
"eslint.workingDirectories": ["./dashboard"],
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
@@ -16,3 +16,6 @@ NEXT_PUBLIC_STRIPE_PK=<nhost_stripe_public_key>
|
||||
NEXT_PUBLIC_GITHUB_APP_INSTALL_URL=<github_app_install_url>
|
||||
NEXT_PUBLIC_ANALYTICS_WRITE_KEY=<analytics_write_key>
|
||||
NEXT_PUBLIC_NHOST_BRAGI_WEBSOCKET=<nhost_bragi_websocket>
|
||||
|
||||
CODEGEN_GRAPHQL_URL=https://local.graphql.nhost.run/v1
|
||||
CODEGEN_HASURA_ADMIN_SECRET=nhost-admin-secret
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- c2ef17c0a: feat: add support for new Team plan
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 7883bbcbd: feat: don't show deprecated plans
|
||||
- 44be6dc0a: feat: set redirectTo during sign-in to support preview environments
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3c3594898: fix: allow access to graphite when configured running in local dashboard
|
||||
- 32c246b7a: chore: update docs icon
|
||||
|
||||
## 1.3.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 174b4165b: chore: use env variables when running graphql codegen
|
||||
- 7c977e714: chore: change `Allowed Roles` to `Default Allowed Roles`
|
||||
- 46f028b9f: fix: remove hardcoded ai version setting
|
||||
|
||||
## 1.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- af33c21d1: chore: remove backendUrl deprecation notice and remove all references to `providersUpdated`
|
||||
|
||||
## 1.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 04784d880: Fix graphite's default version
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
schema:
|
||||
- https://local.graphql.nhost.run/v1:
|
||||
- ${CODEGEN_GRAPHQL_URL}:
|
||||
headers:
|
||||
x-hasura-admin-secret: nhost-admin-secret
|
||||
x-hasura-admin-secret: ${CODEGEN_HASURA_ADMIN_SECRET}
|
||||
generates:
|
||||
src/utils/__generated__/graphql.ts:
|
||||
documents:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "1.2.0",
|
||||
"version": "1.5.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
@@ -10,7 +10,7 @@
|
||||
"start": "next start",
|
||||
"lint": "next lint --max-warnings 0",
|
||||
"test": "vitest",
|
||||
"codegen": "graphql-codegen --config graphql.config.yaml --errors-only",
|
||||
"codegen": "DOTENV_CONFIG_PATH=./.env.local graphql-codegen -r dotenv/config --config graphql.config.yaml --errors-only",
|
||||
"codegen-graphite": "graphql-codegen --config graphite.graphql.config.yaml --errors-only",
|
||||
"format": "prettier --write \"src/**/*.{js,ts,tsx,jsx,json,md}\" --plugin-search-dir=.",
|
||||
"storybook": "start-storybook -p 6006 -s public",
|
||||
|
||||
7
dashboard/public/logos/Note.svg
Normal file
7
dashboard/public/logos/Note.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 6H10" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 8H10" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 10H8" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.79289 13.5H3C2.86739 13.5 2.74021 13.4473 2.64645 13.3536C2.55268 13.2598 2.5 13.1326 2.5 13V3C2.5 2.86739 2.55268 2.74021 2.64645 2.64645C2.74021 2.55268 2.86739 2.5 3 2.5H13C13.1326 2.5 13.2598 2.55268 13.3536 2.64645C13.4473 2.74021 13.5 2.86739 13.5 3V9.79289C13.5 9.85855 13.4871 9.92357 13.4619 9.98423C13.4368 10.0449 13.4 10.1 13.3536 10.1464L10.1464 13.3536C10.1 13.4 10.0449 13.4368 9.98423 13.4619C9.92357 13.4871 9.85855 13.5 9.79289 13.5V13.5Z" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.4548 9.99948H10V13.4545" stroke="#21324B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
7
dashboard/public/logos/light/Note.svg
Normal file
7
dashboard/public/logos/light/Note.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 6H10" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 8H10" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 10H8" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.79289 13.5H3C2.86739 13.5 2.74021 13.4473 2.64645 13.3536C2.55268 13.2598 2.5 13.1326 2.5 13V3C2.5 2.86739 2.55268 2.74021 2.64645 2.64645C2.74021 2.55268 2.86739 2.5 3 2.5H13C13.1326 2.5 13.2598 2.55268 13.3536 2.64645C13.4473 2.74021 13.5 2.86739 13.5 3V9.79289C13.5 9.85855 13.4871 9.92357 13.4619 9.98423C13.4368 10.0449 13.4 10.1 13.3536 10.1464L10.1464 13.3536C10.1 13.4 10.0449 13.4368 9.98423 13.4619C9.92357 13.4871 9.85855 13.5 9.79289 13.5V13.5Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.4548 9.99948H10V13.4545" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -4,9 +4,17 @@ import type { DetailedHTMLProps, HTMLProps } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export interface ContactUsProps
|
||||
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {}
|
||||
extends DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement> {
|
||||
isTeam?: boolean;
|
||||
isOwner?: boolean;
|
||||
}
|
||||
|
||||
export default function FeedbackForm({ className, ...props }: ContactUsProps) {
|
||||
export default function FeedbackForm({
|
||||
className,
|
||||
isTeam,
|
||||
isOwner,
|
||||
...props
|
||||
}: ContactUsProps) {
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
@@ -19,6 +27,30 @@ export default function FeedbackForm({ className, ...props }: ContactUsProps) {
|
||||
Contact us
|
||||
</Text>
|
||||
|
||||
{isTeam && isOwner && (
|
||||
<Text>
|
||||
If this is a new Team project, or you need to manage members, reach
|
||||
out to us on discord or via email at{' '}
|
||||
<Link
|
||||
href="mailto:support@nhost.io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
underline="hover"
|
||||
>
|
||||
support@nhost.io
|
||||
</Link>{' '}
|
||||
so we can have your dedicated channel set up.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{isTeam && !isOwner && (
|
||||
<Text>
|
||||
As part of a team plan you can reach out to us on the private channel
|
||||
for this workspace. If you haven't been added to the channel, ask
|
||||
the workspace owner to add you.
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Text>
|
||||
To report issues with Nhost, please open a GitHub issue in the{' '}
|
||||
<Link
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Alert } from '@/components/ui/v2/Alert';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
|
||||
export default function DepricationNotice() {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
return (
|
||||
!currentProject?.providersUpdated && (
|
||||
<Alert severity="warning" className="grid place-content-center">
|
||||
<Text color="warning" className="max-w-3xl text-sm">
|
||||
On December 1st the old backend domain will cease to work. You need to
|
||||
make sure your client is instantiated using the subdomain and region
|
||||
and update your oauth2 settings. You can find more information{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline"
|
||||
href="https://github.com/nhost/nhost/discussions/2303"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
.
|
||||
</Text>
|
||||
</Alert>
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default as ContactUs } from './DepricationNotice';
|
||||
@@ -13,6 +13,7 @@ import { Dropdown } from '@/components/ui/v2/Dropdown';
|
||||
import { GraphiteIcon } from '@/components/ui/v2/icons/GraphiteIcon';
|
||||
import { DevAssistant } from '@/features/ai/DevAssistant';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { ApplicationStatus } from '@/types/application';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
@@ -37,6 +38,8 @@ export default function Header({ className, ...props }: HeaderProps) {
|
||||
const { currentProject, refetch: refetchProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
|
||||
const isOwner = useIsCurrentUserOwner();
|
||||
|
||||
const isProjectUpdating =
|
||||
currentProject?.appStates[0]?.stateId === ApplicationStatus.Updating;
|
||||
|
||||
@@ -114,7 +117,11 @@ export default function Header({ className, ...props }: HeaderProps) {
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
>
|
||||
<ContactUs className="max-w-md" />
|
||||
<ContactUs
|
||||
className="max-w-md"
|
||||
isTeam={currentProject?.plan?.name === 'Team'}
|
||||
isOwner={isOwner}
|
||||
/>
|
||||
</Dropdown.Content>
|
||||
</Dropdown.Root>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
|
||||
import type { ProjectLayoutProps } from '@/components/layout/ProjectLayout';
|
||||
import { ProjectLayout } from '@/components/layout/ProjectLayout';
|
||||
import type { SettingsSidebarProps } from '@/components/layout/SettingsSidebar';
|
||||
@@ -50,7 +49,6 @@ export default function SettingsLayout({
|
||||
>
|
||||
<RetryableErrorBoundary>
|
||||
<div className="flex flex-col space-y-2">
|
||||
<DepricationNotice />
|
||||
{hasGitRepo && (
|
||||
<Alert
|
||||
severity="warning"
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { Alert } from '@/components/ui/v2/Alert';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { ArrowSquareOutIcon } from '@/components/ui/v2/icons/ArrowSquareOutIcon';
|
||||
import { Link } from '@/components/ui/v2/Link';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { useConfirmProvidersUpdatedMutation } from '@/utils/__generated__/graphql';
|
||||
import { useTheme } from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
export default function ProvidersUpdatedAlert() {
|
||||
const theme = useTheme();
|
||||
const { openAlertDialog } = useDialog();
|
||||
const [confirmed, setConfirmed] = useState(true);
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const [confirmProvidersUpdated] = useConfirmProvidersUpdatedMutation({
|
||||
variables: { id: currentProject?.id },
|
||||
});
|
||||
|
||||
async function handleSubmitConfirmation() {
|
||||
const confirmProvidersUpdatedPromise = confirmProvidersUpdated();
|
||||
|
||||
await toast.promise(
|
||||
confirmProvidersUpdatedPromise,
|
||||
{
|
||||
loading: 'Confirming...',
|
||||
success: 'Your settings have been updated successfully.',
|
||||
error: 'An error occurred while trying to confirm the message.',
|
||||
},
|
||||
getToastStyleProps(),
|
||||
);
|
||||
|
||||
setConfirmed(false);
|
||||
}
|
||||
|
||||
function handleOpenConfirmationDialog() {
|
||||
openAlertDialog({
|
||||
title: 'Confirm all providers updated?',
|
||||
payload: (
|
||||
<Text variant="subtitle1" component="span">
|
||||
Please make sure to update all providers before continuing. Your
|
||||
sign-in flows might break if you don't.
|
||||
</Text>
|
||||
),
|
||||
props: {
|
||||
onPrimaryAction: handleSubmitConfirmation,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (!confirmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert
|
||||
severity="warning"
|
||||
className="grid items-center grid-flow-row gap-2 p-4 place-items-center lg:grid-flow-col lg:place-content-between"
|
||||
>
|
||||
<div className="grid grid-flow-row gap-1 text-left">
|
||||
<Text className="font-semibold">
|
||||
Please update the Redirect URL for all providers being used
|
||||
</Text>
|
||||
|
||||
<Text className="text-sm+">
|
||||
We are deprecating your project's old DNS name in favor of
|
||||
individual DNS names for each service. Please make sure to update your
|
||||
providers to use the new auth specific URL under <b>Redirect URL</b>{' '}
|
||||
before the 1st of February 2023.{' '}
|
||||
<Link
|
||||
href="https://github.com/nhost/nhost/discussions/1319"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
underline="hover"
|
||||
className="font-medium"
|
||||
>
|
||||
Read the discussion here.
|
||||
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
|
||||
</Link>
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="borderless"
|
||||
className={
|
||||
theme.palette.mode === 'dark'
|
||||
? 'text-white hover:bg-brown'
|
||||
: 'text-black hover:bg-orange-300'
|
||||
}
|
||||
onClick={handleOpenConfirmationDialog}
|
||||
>
|
||||
I have updated all Redirect URLs
|
||||
</Button>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default as ProvidersUpdatedAlert } from './ProvidersUpdatedAlert';
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
} from '@/features/ai/DevAssistant/state';
|
||||
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsGraphiteEnabled } from '@/features/projects/common/hooks/useIsGraphiteEnabled';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import {
|
||||
useSendDevMessageMutation,
|
||||
@@ -33,6 +35,7 @@ export type Message = Omit<
|
||||
>;
|
||||
|
||||
export default function DevAssistant() {
|
||||
const isPlatform = useIsPlatform();
|
||||
const { currentProject, currentWorkspace } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -45,6 +48,8 @@ export default function DevAssistant() {
|
||||
const [startDevSession] = useStartDevSessionMutation({ client: adminClient });
|
||||
const [sendDevMessage] = useSendDevMessageMutation({ client: adminClient });
|
||||
|
||||
const { isGraphiteEnabled } = useIsGraphiteEnabled();
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -141,7 +146,7 @@ export default function DevAssistant() {
|
||||
}
|
||||
};
|
||||
|
||||
if (currentProject.plan.isFree) {
|
||||
if (isPlatform && currentProject?.plan?.isFree) {
|
||||
return (
|
||||
<Box className="p-4">
|
||||
<UpgradeToProBanner
|
||||
@@ -157,7 +162,12 @@ export default function DevAssistant() {
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentProject.plan.isFree && !currentProject.config?.ai) {
|
||||
if (
|
||||
(isPlatform &&
|
||||
!currentProject?.plan?.isFree &&
|
||||
!currentProject.config?.ai) ||
|
||||
!isGraphiteEnabled
|
||||
) {
|
||||
return (
|
||||
<Box className="p-4">
|
||||
<Alert className="grid w-full grid-flow-col place-content-between items-center gap-2">
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Alert } from '@/components/ui/v2/Alert';
|
||||
import { filterOptions } from '@/components/ui/v2/Autocomplete';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
@@ -95,8 +94,8 @@ export default function AISettings() {
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: {
|
||||
version: {
|
||||
label: '0.1.0-beta4',
|
||||
value: '0.1.0-beta4',
|
||||
label: ai?.version ?? availableVersions?.at(0)?.label,
|
||||
value: ai?.version ?? availableVersions?.at(0)?.value,
|
||||
},
|
||||
webhookSecret: '',
|
||||
organization: '',
|
||||
@@ -110,12 +109,17 @@ export default function AISettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
const { register, formState, reset, watch } = form;
|
||||
const { register, formState, reset, watch, setValue } = form;
|
||||
|
||||
const aiSettingsFormValues = watch();
|
||||
|
||||
useEffect(() => {
|
||||
if (ai) {
|
||||
reset({
|
||||
version: { label: ai?.version, value: ai?.version },
|
||||
version: {
|
||||
label: ai?.version,
|
||||
value: ai?.version,
|
||||
},
|
||||
webhookSecret: ai?.webhookSecret,
|
||||
synchPeriodMinutes: ai?.autoEmbeddings?.synchPeriodMinutes,
|
||||
apiKey: ai?.openai?.apiKey,
|
||||
@@ -130,10 +134,27 @@ export default function AISettings() {
|
||||
setAIServiceEnabled(!!ai);
|
||||
}, [ai, reset]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!loadingGraphiteVersionsData &&
|
||||
availableVersions.length > 0 &&
|
||||
!ai &&
|
||||
!aiSettingsFormValues.version.value
|
||||
) {
|
||||
setValue('version', availableVersions?.at(0));
|
||||
}
|
||||
}, [
|
||||
ai,
|
||||
setValue,
|
||||
availableVersions,
|
||||
aiSettingsFormValues,
|
||||
loadingGraphiteVersionsData,
|
||||
]);
|
||||
|
||||
const toggleAIService = async (enabled: boolean) => {
|
||||
setAIServiceEnabled(enabled);
|
||||
|
||||
if (!enabled) {
|
||||
if (!enabled && ai) {
|
||||
openDialog({
|
||||
title: 'Confirm Disabling the AI service',
|
||||
component: (
|
||||
@@ -203,8 +224,6 @@ export default function AISettings() {
|
||||
}
|
||||
}
|
||||
|
||||
const aiSettingsFormValues = watch();
|
||||
|
||||
const getAIResourcesCost = () => {
|
||||
const vCPUs = `${
|
||||
aiSettingsFormValues.compute.cpu / RESOURCE_VCPU_MULTIPLIER
|
||||
@@ -240,37 +259,54 @@ export default function AISettings() {
|
||||
className="flex flex-col"
|
||||
>
|
||||
<Box className="space-y-4">
|
||||
<Box className="space-y-2">
|
||||
<Box className="flex flex-row items-center space-x-2">
|
||||
<Text className="text-lg font-semibold">Version</Text>
|
||||
<Tooltip title="Version of the service to use.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<ControlledAutocomplete
|
||||
id="version"
|
||||
name="version"
|
||||
filterOptions={(options, state) => {
|
||||
if (state.inputValue === ai?.version) {
|
||||
return options;
|
||||
{availableVersions.length > 0 && (
|
||||
<Box className="space-y-2">
|
||||
<Box className="flex flex-row items-center space-x-2">
|
||||
<Text className="text-lg font-semibold">Version</Text>
|
||||
<Tooltip title="Version of the service to use.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<ControlledAutocomplete
|
||||
id="version"
|
||||
name="version"
|
||||
autoHighlight
|
||||
isOptionEqualToValue={() => false}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
const inputValueLower = inputValue.toLowerCase();
|
||||
const matched = [];
|
||||
const otherOptions = [];
|
||||
|
||||
options.forEach((option) => {
|
||||
const optionLabelLower = option.label.toLowerCase();
|
||||
|
||||
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||
matched.push(option);
|
||||
} else {
|
||||
otherOptions.push(option);
|
||||
}
|
||||
});
|
||||
|
||||
const result = [...matched, ...otherOptions];
|
||||
|
||||
return result;
|
||||
}}
|
||||
fullWidth
|
||||
className="col-span-4"
|
||||
options={availableVersions}
|
||||
error={!!formState.errors?.version?.message}
|
||||
helperText={formState.errors?.version?.message}
|
||||
showCustomOption="auto"
|
||||
customOptionLabel={(value) =>
|
||||
`Use custom value: "${value}"`
|
||||
}
|
||||
return filterOptions(options, state);
|
||||
}}
|
||||
fullWidth
|
||||
className="col-span-4"
|
||||
options={availableVersions}
|
||||
error={!!formState.errors?.version?.message}
|
||||
helperText={formState.errors?.version?.message}
|
||||
showCustomOption="auto"
|
||||
customOptionLabel={(value) =>
|
||||
`Use custom value: "${value}"`
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box className="space-y-2">
|
||||
<Box className="flex flex-row items-center space-x-2">
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { filterOptions } from '@/components/ui/v2/Autocomplete';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import {
|
||||
GetAuthenticationSettingsDocument,
|
||||
@@ -134,12 +133,26 @@ export default function AuthServiceVersionSettings() {
|
||||
<ControlledAutocomplete
|
||||
id="version"
|
||||
name="version"
|
||||
filterOptions={(options, state) => {
|
||||
if (state.inputValue === version) {
|
||||
return options;
|
||||
}
|
||||
autoHighlight
|
||||
isOptionEqualToValue={() => false}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
const inputValueLower = inputValue.toLowerCase();
|
||||
const matched = [];
|
||||
const otherOptions = [];
|
||||
|
||||
return filterOptions(options, state);
|
||||
options.forEach((option) => {
|
||||
const optionLabelLower = option.label.toLowerCase();
|
||||
|
||||
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||
matched.push(option);
|
||||
} else {
|
||||
otherOptions.push(option);
|
||||
}
|
||||
});
|
||||
|
||||
const result = [...matched, ...otherOptions];
|
||||
|
||||
return result;
|
||||
}}
|
||||
fullWidth
|
||||
className="lg:col-span-2"
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { filterOptions } from '@/components/ui/v2/Autocomplete';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import {
|
||||
GetPostgresSettingsDocument,
|
||||
@@ -136,12 +135,26 @@ export default function DatabaseServiceVersionSettings() {
|
||||
<ControlledAutocomplete
|
||||
id="version"
|
||||
name="version"
|
||||
filterOptions={(options, state) => {
|
||||
if (state.inputValue === version) {
|
||||
return options;
|
||||
}
|
||||
autoHighlight
|
||||
isOptionEqualToValue={() => false}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
const inputValueLower = inputValue.toLowerCase();
|
||||
const matched = [];
|
||||
const otherOptions = [];
|
||||
|
||||
return filterOptions(options, state);
|
||||
options.forEach((option) => {
|
||||
const optionLabelLower = option.label.toLowerCase();
|
||||
|
||||
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||
matched.push(option);
|
||||
} else {
|
||||
otherOptions.push(option);
|
||||
}
|
||||
});
|
||||
|
||||
const result = [...matched, ...otherOptions];
|
||||
|
||||
return result;
|
||||
}}
|
||||
fullWidth
|
||||
className="lg:col-span-2"
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { filterOptions } from '@/components/ui/v2/Autocomplete';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import {
|
||||
GetHasuraSettingsDocument,
|
||||
@@ -136,12 +135,26 @@ export default function HasuraServiceVersionSettings() {
|
||||
<ControlledAutocomplete
|
||||
id="version"
|
||||
name="version"
|
||||
filterOptions={(options, state) => {
|
||||
if (state.inputValue === version) {
|
||||
return options;
|
||||
}
|
||||
autoHighlight
|
||||
isOptionEqualToValue={() => false}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
const inputValueLower = inputValue.toLowerCase();
|
||||
const matched = [];
|
||||
const otherOptions = [];
|
||||
|
||||
return filterOptions(options, state);
|
||||
options.forEach((option) => {
|
||||
const optionLabelLower = option.label.toLowerCase();
|
||||
|
||||
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||
matched.push(option);
|
||||
} else {
|
||||
otherOptions.push(option);
|
||||
}
|
||||
});
|
||||
|
||||
const result = [...matched, ...otherOptions];
|
||||
|
||||
return result;
|
||||
}}
|
||||
fullWidth
|
||||
className="lg:col-span-2"
|
||||
|
||||
@@ -107,7 +107,7 @@ export default function WorkspaceSidebar({
|
||||
<div className="grid grid-flow-row gap-2">
|
||||
<Resource
|
||||
text="Documentation"
|
||||
logo="Question"
|
||||
logo="Note"
|
||||
link="https://docs.nhost.io"
|
||||
/>
|
||||
<Resource
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/
|
||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||
import { getHasuraAdminSecret } from '@/utils/env';
|
||||
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export default function useAdminApolloClient() {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
@@ -12,18 +13,24 @@ export default function useAdminApolloClient() {
|
||||
'graphql',
|
||||
);
|
||||
|
||||
const adminClient = new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
link: new HttpLink({
|
||||
uri: serviceUrl,
|
||||
headers: {
|
||||
'x-hasura-admin-secret':
|
||||
process.env.NEXT_PUBLIC_ENV === 'dev'
|
||||
? getHasuraAdminSecret()
|
||||
: currentProject?.config?.hasura.adminSecret,
|
||||
},
|
||||
}),
|
||||
});
|
||||
const projectAdminSecret = currentProject.config?.hasura?.adminSecret;
|
||||
|
||||
const adminClient = useMemo(
|
||||
() =>
|
||||
new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
link: new HttpLink({
|
||||
uri: serviceUrl,
|
||||
headers: {
|
||||
'x-hasura-admin-secret':
|
||||
process.env.NEXT_PUBLIC_ENV === 'dev'
|
||||
? getHasuraAdminSecret()
|
||||
: projectAdminSecret,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
[serviceUrl, projectAdminSecret],
|
||||
);
|
||||
|
||||
return {
|
||||
adminClient,
|
||||
|
||||
@@ -114,7 +114,6 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
|
||||
createdAt: new Date().toISOString(),
|
||||
desiredState: ApplicationStatus.Live,
|
||||
featureFlags: [],
|
||||
providersUpdated: true,
|
||||
repositoryProductionBranch: null,
|
||||
nhostBaseFolder: null,
|
||||
plan: null,
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function useIsCurrentUserOwner() {
|
||||
const { currentWorkspace, loading } = useCurrentWorkspaceAndProject();
|
||||
const currentUser = useUserData();
|
||||
|
||||
if (loading || !currentWorkspace.workspaceMembers || !currentUser) {
|
||||
if (loading || !currentWorkspace?.workspaceMembers || !currentUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default as useIsGraphiteEnabled } from './useIsGraphiteEnabled';
|
||||
@@ -0,0 +1,14 @@
|
||||
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||
import { useGetGraphiteSessionsQuery } from '@/utils/__generated__/graphite.graphql';
|
||||
|
||||
export default function useIsGraphiteEnabled() {
|
||||
const { adminClient } = useAdminApolloClient();
|
||||
|
||||
const { error } = useGetGraphiteSessionsQuery({
|
||||
client: adminClient,
|
||||
});
|
||||
|
||||
return {
|
||||
isGraphiteEnabled: !error,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
const planDescriptions = {
|
||||
Starter: '1 GB database, 5 GB of file storage, 10 GB of network traffic.',
|
||||
Pro: '10 GB database, 25 GB of file storage, 50 GB of network traffic, and backups.',
|
||||
Team: 'Reach out to us at support@nhost.io to have your private channel set up.',
|
||||
};
|
||||
|
||||
export default planDescriptions;
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function OverviewTopBar() {
|
||||
const isPlatform = useIsPlatform();
|
||||
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
|
||||
const isOwner = useIsCurrentUserOwner();
|
||||
const isPro = !currentProject?.plan?.isFree;
|
||||
const isStarter = currentProject?.plan?.name === 'Starter';
|
||||
const { openDialog } = useDialog();
|
||||
const { maintenanceActive } = useUI();
|
||||
|
||||
@@ -65,7 +65,6 @@ export default function OverviewTopBar() {
|
||||
>
|
||||
{currentProject.name}
|
||||
</Text>
|
||||
|
||||
{currentProject.creator && (
|
||||
<Text
|
||||
color="secondary"
|
||||
@@ -81,15 +80,14 @@ export default function OverviewTopBar() {
|
||||
ago
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<div className="mt-1 inline-grid grid-flow-col items-center justify-start gap-2 md:mt-0">
|
||||
<Chip
|
||||
size="small"
|
||||
label={isPro ? 'Pro' : 'Starter'}
|
||||
color={isPro ? 'primary' : 'default'}
|
||||
label={currentProject.plan.name}
|
||||
color={!isStarter ? 'primary' : 'default'}
|
||||
/>
|
||||
|
||||
{!isPro && isOwner && (
|
||||
{isStarter && isOwner && (
|
||||
<Button
|
||||
variant="borderless"
|
||||
className="mr-2"
|
||||
|
||||
@@ -36,7 +36,7 @@ export interface RoleSettingsFormValues {
|
||||
*/
|
||||
authUserDefaultRole: string;
|
||||
/**
|
||||
* Allowed roles for the project.
|
||||
* Default Allowed roles for the project.
|
||||
*/
|
||||
authUserDefaultAllowedRoles: Role[];
|
||||
}
|
||||
@@ -169,8 +169,8 @@ export default function RoleSettings() {
|
||||
|
||||
return (
|
||||
<SettingsContainer
|
||||
title="Allowed Roles"
|
||||
description="Allowed roles are roles users get automatically when they sign up."
|
||||
title="Default Allowed Roles"
|
||||
description="Default Allowed Roles are roles users get automatically when they sign up."
|
||||
docsLink="https://docs.nhost.io/authentication/users#allowed-roles"
|
||||
rootClassName="gap-0"
|
||||
className={twMerge(
|
||||
|
||||
@@ -80,7 +80,7 @@ function AllWorkspaceApps() {
|
||||
|
||||
<Chip
|
||||
size="small"
|
||||
label={project.plan.isFree ? 'Starter' : 'Pro'}
|
||||
label={project.plan.name}
|
||||
color={project.plan.isFree ? 'default' : 'primary'}
|
||||
/>
|
||||
</ListItem.Button>
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { filterOptions } from '@/components/ui/v2/Autocomplete';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import {
|
||||
GetStorageSettingsDocument,
|
||||
@@ -136,12 +135,26 @@ export default function StorageServiceVersionSettings() {
|
||||
<ControlledAutocomplete
|
||||
id="version"
|
||||
name="version"
|
||||
filterOptions={(options, state) => {
|
||||
if (state.inputValue === version) {
|
||||
return options;
|
||||
}
|
||||
autoHighlight
|
||||
isOptionEqualToValue={() => false}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
const inputValueLower = inputValue.toLowerCase();
|
||||
const matched = [];
|
||||
const otherOptions = [];
|
||||
|
||||
return filterOptions(options, state);
|
||||
options.forEach((option) => {
|
||||
const optionLabelLower = option.label.toLowerCase();
|
||||
|
||||
if (optionLabelLower.startsWith(inputValueLower)) {
|
||||
matched.push(option);
|
||||
} else {
|
||||
otherOptions.push(option);
|
||||
}
|
||||
});
|
||||
|
||||
const result = [...matched, ...otherOptions];
|
||||
|
||||
return result;
|
||||
}}
|
||||
fullWidth
|
||||
className="lg:col-span-2"
|
||||
|
||||
@@ -32,7 +32,7 @@ query PrefetchNewApp {
|
||||
regions(order_by: { city: asc }) {
|
||||
...PrefetchNewAppRegions
|
||||
}
|
||||
plans(order_by: { sort: asc }) {
|
||||
plans(order_by: {sort: asc}, where: {deprecated: {_eq: false}}) {
|
||||
...PrefetchNewAppPlans
|
||||
}
|
||||
workspaces {
|
||||
|
||||
@@ -7,7 +7,6 @@ fragment Project on apps {
|
||||
createdAt
|
||||
desiredState
|
||||
nhostBaseFolder
|
||||
providersUpdated
|
||||
config(resolve: true) {
|
||||
observability {
|
||||
grafana {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
query getGraphiteSessions {
|
||||
graphite {
|
||||
sessions {
|
||||
sessionID
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
mutation confirmProvidersUpdated($id: uuid!) {
|
||||
updateApp(pk_columns: { id: $id }, _set: { providersUpdated: true }) {
|
||||
id
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,14 @@ import { Link } from '@/components/ui/v2/Link';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { AssistantForm } from '@/features/ai/AssistantForm';
|
||||
import { AssistantsList } from '@/features/ai/AssistantsList';
|
||||
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||
import { getHasuraAdminSecret } from '@/utils/env';
|
||||
import { useIsGraphiteEnabled } from '@/features/projects/common/hooks/useIsGraphiteEnabled';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
useGetAssistantsQuery,
|
||||
type GetAssistantsQuery,
|
||||
} from '@/utils/__generated__/graphite.graphql';
|
||||
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
|
||||
import { useMemo, type ReactElement } from 'react';
|
||||
|
||||
export type Assistant = Omit<
|
||||
@@ -26,34 +26,15 @@ export type Assistant = Omit<
|
||||
|
||||
export default function AssistantsPage() {
|
||||
const { openDrawer } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
|
||||
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
|
||||
const adminSecret = currentProject?.config?.hasura?.adminSecret;
|
||||
const { adminClient } = useAdminApolloClient();
|
||||
const { isGraphiteEnabled } = useIsGraphiteEnabled();
|
||||
|
||||
const serviceUrl = generateAppServiceUrl(
|
||||
currentProject?.subdomain,
|
||||
currentProject?.region,
|
||||
'graphql',
|
||||
);
|
||||
|
||||
const client = useMemo(
|
||||
() =>
|
||||
new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
link: new HttpLink({
|
||||
uri: serviceUrl,
|
||||
headers: {
|
||||
'x-hasura-admin-secret':
|
||||
process.env.NEXT_PUBLIC_ENV === 'dev'
|
||||
? getHasuraAdminSecret()
|
||||
: adminSecret,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
[serviceUrl, adminSecret],
|
||||
);
|
||||
|
||||
const { data, loading, refetch } = useGetAssistantsQuery({ client });
|
||||
const { data, loading, refetch } = useGetAssistantsQuery({
|
||||
client: adminClient,
|
||||
});
|
||||
|
||||
const assistants = useMemo(() => data?.graphite?.assistants || [], [data]);
|
||||
|
||||
@@ -64,7 +45,7 @@ export default function AssistantsPage() {
|
||||
});
|
||||
};
|
||||
|
||||
if (currentProject.plan.isFree) {
|
||||
if (isPlatform && currentProject?.plan?.isFree) {
|
||||
return (
|
||||
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
|
||||
<UpgradeToProBanner
|
||||
@@ -80,7 +61,12 @@ export default function AssistantsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentProject.plan.isFree && !currentProject.config?.ai) {
|
||||
if (
|
||||
(isPlatform &&
|
||||
!currentProject?.plan?.isFree &&
|
||||
!currentProject.config?.ai) ||
|
||||
!isGraphiteEnabled
|
||||
) {
|
||||
return (
|
||||
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
|
||||
<Alert className="grid w-full grid-flow-col place-content-between items-center gap-2">
|
||||
|
||||
@@ -11,14 +11,14 @@ import { Link } from '@/components/ui/v2/Link';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { AutoEmbeddingsForm } from '@/features/ai/AutoEmbeddingsForm';
|
||||
import { AutoEmbeddingsList } from '@/features/ai/AutoEmbeddingsList';
|
||||
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||
import { getHasuraAdminSecret } from '@/utils/env';
|
||||
import { useIsGraphiteEnabled } from '@/features/projects/common/hooks/useIsGraphiteEnabled';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
useGetGraphiteAutoEmbeddingsConfigurationsQuery,
|
||||
type GetGraphiteAutoEmbeddingsConfigurationsQuery,
|
||||
} from '@/utils/__generated__/graphite.graphql';
|
||||
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useMemo, useRef, useState, type ReactElement } from 'react';
|
||||
|
||||
@@ -32,43 +32,22 @@ export default function AutoEmbeddingsPage() {
|
||||
const router = useRouter();
|
||||
const { openDrawer } = useDialog();
|
||||
|
||||
const isPlatform = useIsPlatform();
|
||||
|
||||
const { currentWorkspace, currentProject } = useCurrentWorkspaceAndProject();
|
||||
const adminSecret = currentProject?.config?.hasura?.adminSecret;
|
||||
|
||||
const serviceUrl = generateAppServiceUrl(
|
||||
currentProject?.subdomain,
|
||||
currentProject?.region,
|
||||
'graphql',
|
||||
);
|
||||
|
||||
const client = useMemo(
|
||||
() =>
|
||||
new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
link: new HttpLink({
|
||||
uri: serviceUrl,
|
||||
headers: {
|
||||
'x-hasura-admin-secret':
|
||||
process.env.NEXT_PUBLIC_ENV === 'dev'
|
||||
? getHasuraAdminSecret()
|
||||
: adminSecret,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
[serviceUrl, adminSecret],
|
||||
);
|
||||
const { adminClient } = useAdminApolloClient();
|
||||
const { isGraphiteEnabled } = useIsGraphiteEnabled();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(
|
||||
parseInt(router.query.page as string, 10) || 1,
|
||||
);
|
||||
|
||||
const [nrOfPages, setNrOfPages] = useState(0);
|
||||
|
||||
const offset = useMemo(() => currentPage - 1, [currentPage]);
|
||||
|
||||
const { data, loading, refetch } =
|
||||
useGetGraphiteAutoEmbeddingsConfigurationsQuery({
|
||||
client,
|
||||
client: adminClient,
|
||||
variables: {
|
||||
limit: limit.current,
|
||||
offset,
|
||||
@@ -102,7 +81,7 @@ export default function AutoEmbeddingsPage() {
|
||||
});
|
||||
};
|
||||
|
||||
if (currentProject.plan.isFree) {
|
||||
if (isPlatform && currentProject?.plan?.isFree) {
|
||||
return (
|
||||
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
|
||||
<UpgradeToProBanner
|
||||
@@ -118,7 +97,12 @@ export default function AutoEmbeddingsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentProject.plan.isFree && !currentProject.config?.ai) {
|
||||
if (
|
||||
(isPlatform &&
|
||||
!currentProject?.plan?.isFree &&
|
||||
!currentProject.config?.ai) ||
|
||||
!isGraphiteEnabled
|
||||
) {
|
||||
return (
|
||||
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
|
||||
<Alert className="grid w-full grid-flow-col place-content-between items-center gap-2">
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
|
||||
import { Container } from '@/components/layout/Container';
|
||||
import { ProjectLayout } from '@/components/layout/ProjectLayout';
|
||||
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
|
||||
@@ -68,10 +67,5 @@ export default function BackupsPage() {
|
||||
}
|
||||
|
||||
BackupsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<ProjectLayout>
|
||||
<DepricationNotice />
|
||||
{page}
|
||||
</ProjectLayout>
|
||||
);
|
||||
return <ProjectLayout>{page}</ProjectLayout>;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
|
||||
import { InlineCode } from '@/components/presentational/InlineCode';
|
||||
import { DataBrowserEmptyState } from '@/features/database/dataGrid/components/DataBrowserEmptyState';
|
||||
import { DataBrowserLayout } from '@/features/database/dataGrid/components/DataBrowserLayout';
|
||||
@@ -38,7 +37,6 @@ DataBrowserDatabaseDetailsPage.getLayout = function getLayout(
|
||||
) {
|
||||
return (
|
||||
<DataBrowserLayout>
|
||||
<DepricationNotice />
|
||||
{page}
|
||||
</DataBrowserLayout>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
|
||||
import { Container } from '@/components/layout/Container';
|
||||
import { ProjectLayout } from '@/components/layout/ProjectLayout';
|
||||
import type { DeploymentStatus } from '@/components/presentational/StatusCircle';
|
||||
@@ -153,10 +152,5 @@ export default function DeploymentDetailsPage() {
|
||||
}
|
||||
|
||||
DeploymentDetailsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<ProjectLayout>
|
||||
<DepricationNotice />
|
||||
{page}
|
||||
</ProjectLayout>
|
||||
);
|
||||
return <ProjectLayout>{page}</ProjectLayout>;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Container } from '@/components/layout/Container';
|
||||
import { ProjectLayout } from '@/components/layout/ProjectLayout';
|
||||
@@ -68,10 +67,5 @@ export default function DeploymentsPage() {
|
||||
}
|
||||
|
||||
DeploymentsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<ProjectLayout>
|
||||
<DepricationNotice />
|
||||
{page}
|
||||
</ProjectLayout>
|
||||
);
|
||||
return <ProjectLayout>{page}</ProjectLayout>;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
|
||||
import { ProjectLayout } from '@/components/layout/ProjectLayout';
|
||||
import { LoadingScreen } from '@/components/presentational/LoadingScreen';
|
||||
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
|
||||
@@ -352,7 +351,6 @@ GraphQLPage.getLayout = function getLayout(page: ReactElement) {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DepricationNotice />
|
||||
{page}
|
||||
</ProjectLayout>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
|
||||
import { Container } from '@/components/layout/Container';
|
||||
import { ProjectLayout } from '@/components/layout/ProjectLayout';
|
||||
import { LoadingScreen } from '@/components/presentational/LoadingScreen';
|
||||
@@ -113,10 +112,5 @@ export default function HasuraPage() {
|
||||
}
|
||||
|
||||
HasuraPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<ProjectLayout>
|
||||
<DepricationNotice />
|
||||
{page}
|
||||
</ProjectLayout>
|
||||
);
|
||||
return <ProjectLayout>{page}</ProjectLayout>;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
|
||||
import { ProjectLayout } from '@/components/layout/ProjectLayout';
|
||||
import { ApplicationErrored } from '@/features/projects/common/components/ApplicationErrored';
|
||||
import { ApplicationLive } from '@/features/projects/common/components/ApplicationLive';
|
||||
@@ -50,10 +49,5 @@ export default function AppIndexPage() {
|
||||
}
|
||||
|
||||
AppIndexPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<ProjectLayout>
|
||||
<DepricationNotice />
|
||||
{page}
|
||||
</ProjectLayout>
|
||||
);
|
||||
return <ProjectLayout>{page}</ProjectLayout>;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
|
||||
import { ProjectLayout } from '@/components/layout/ProjectLayout';
|
||||
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
@@ -135,10 +134,5 @@ export default function LogsPage() {
|
||||
}
|
||||
|
||||
LogsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<ProjectLayout>
|
||||
<DepricationNotice />
|
||||
{page}
|
||||
</ProjectLayout>
|
||||
);
|
||||
return <ProjectLayout>{page}</ProjectLayout>;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
|
||||
import { Container } from '@/components/layout/Container';
|
||||
import { ProjectLayout } from '@/components/layout/ProjectLayout';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
@@ -127,10 +126,5 @@ export default function MetricsPage() {
|
||||
}
|
||||
|
||||
MetricsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<ProjectLayout>
|
||||
<DepricationNotice />
|
||||
{page}
|
||||
</ProjectLayout>
|
||||
);
|
||||
return <ProjectLayout>{page}</ProjectLayout>;
|
||||
};
|
||||
|
||||
@@ -12,7 +12,6 @@ import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/
|
||||
import type { GetRunServicesQuery } from '@/utils/__generated__/graphql';
|
||||
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql';
|
||||
|
||||
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
|
||||
import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification';
|
||||
import {
|
||||
ServiceForm,
|
||||
@@ -259,10 +258,5 @@ export default function ServicesPage() {
|
||||
}
|
||||
|
||||
ServicesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<ProjectLayout>
|
||||
<DepricationNotice />
|
||||
{page}
|
||||
</ProjectLayout>
|
||||
);
|
||||
return <ProjectLayout>{page}</ProjectLayout>;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Container } from '@/components/layout/Container';
|
||||
import { SettingsLayout } from '@/components/layout/SettingsLayout';
|
||||
import { ProvidersUpdatedAlert } from '@/components/settings';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { AnonymousSignInSettings } from '@/features/authentication/settings/components/AnonymousSignInSettings';
|
||||
import { AppleProviderSettings } from '@/features/authentication/settings/components/AppleProviderSettings';
|
||||
@@ -55,7 +54,6 @@ export default function SettingsSignInMethodsPage() {
|
||||
<WebAuthnSettings />
|
||||
<AnonymousSignInSettings />
|
||||
<SMSSettings />
|
||||
{!currentProject.providersUpdated && <ProvidersUpdatedAlert />}
|
||||
<AppleProviderSettings />
|
||||
<AzureADProviderSettings />
|
||||
<DiscordProviderSettings />
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
|
||||
import { ProjectLayout } from '@/components/layout/ProjectLayout';
|
||||
import { LoadingScreen } from '@/components/presentational/LoadingScreen';
|
||||
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
|
||||
@@ -45,7 +44,6 @@ StoragePage.getLayout = function getLayout(page: ReactElement) {
|
||||
<ProjectLayout
|
||||
mainContainerProps={{ sx: { backgroundColor: 'background.default' } }}
|
||||
>
|
||||
<DepricationNotice />
|
||||
{page}
|
||||
</ProjectLayout>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import DepricationNotice from '@/components/common/DepricationNotice/DepricationNotice';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { Pagination } from '@/components/common/Pagination';
|
||||
import { Container } from '@/components/layout/Container';
|
||||
@@ -391,7 +390,6 @@ export default function UsersPage() {
|
||||
UsersPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<ProjectLayout contentContainerProps={{ className: 'h-full' }}>
|
||||
<DepricationNotice />
|
||||
{page}
|
||||
</ProjectLayout>
|
||||
);
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
import { useUserData } from '@nhost/nextjs';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { FormEvent, ReactElement } from 'react';
|
||||
import { cloneElement, isValidElement, useState } from 'react';
|
||||
@@ -444,6 +445,19 @@ export function NewProjectPageContent({
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
<Text variant="subtitle2">
|
||||
Select a plan that suits your infrastructure needs.{' '}
|
||||
<Link href="https://nhost.io/pricing">
|
||||
<a
|
||||
href="https://nhost.io/pricing"
|
||||
className="underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</Link>
|
||||
</Text>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Button } from '@/components/ui/v2/Button';
|
||||
import { Divider } from '@/components/ui/v2/Divider';
|
||||
import { GitHubIcon } from '@/components/ui/v2/icons/GitHubIcon';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useHostName } from '@/features/projects/common/hooks/useHostName';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { nhost } from '@/utils/nhost';
|
||||
import type { ReactElement } from 'react';
|
||||
@@ -14,6 +15,8 @@ import { toast } from 'react-hot-toast';
|
||||
export default function SignUpPage() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const redirectTo = useHostName();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text
|
||||
@@ -24,7 +27,7 @@ export default function SignUpPage() {
|
||||
It's time to build
|
||||
</Text>
|
||||
|
||||
<Box className="grid grid-flow-row gap-4 rounded-md border bg-transparent p-6 lg:p-12">
|
||||
<Box className="grid grid-flow-row gap-4 p-6 bg-transparent border rounded-md lg:p-12">
|
||||
<Button
|
||||
className="!bg-white !text-black hover:ring-2 hover:ring-white hover:ring-opacity-50 disabled:!text-black disabled:!text-opacity-60"
|
||||
startIcon={<GitHubIcon />}
|
||||
@@ -35,7 +38,10 @@ export default function SignUpPage() {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
await nhost.auth.signIn({ provider: 'github' });
|
||||
await nhost.auth.signIn({
|
||||
provider: 'github',
|
||||
options: { redirectTo },
|
||||
});
|
||||
} catch {
|
||||
toast.error(
|
||||
`An error occurred while trying to sign in using GitHub. Please try again later.`,
|
||||
@@ -61,7 +67,7 @@ export default function SignUpPage() {
|
||||
|
||||
<Divider className="!my-2" />
|
||||
|
||||
<Text color="secondary" className="text-center text-sm">
|
||||
<Text color="secondary" className="text-sm text-center">
|
||||
By clicking continue, you agree to our{' '}
|
||||
<NavLink
|
||||
href="https://nhost.io/legal/terms-of-service"
|
||||
|
||||
@@ -59,7 +59,6 @@ export const mockApplication: Project = {
|
||||
deployments: [],
|
||||
desiredState: ApplicationStatus.Live,
|
||||
featureFlags: [],
|
||||
providersUpdated: true,
|
||||
githubRepository: { fullName: 'test/git-project' },
|
||||
repositoryProductionBranch: null,
|
||||
nhostBaseFolder: null,
|
||||
|
||||
@@ -7283,6 +7283,11 @@ export type GetAssistantsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
export type GetAssistantsQuery = { __typename?: 'query_root', graphite?: { __typename?: 'graphiteQuery', assistants: Array<{ __typename?: 'graphiteAssistant', assistantID: string, name: string, description: string, model: string, instructions: string, graphql?: Array<{ __typename?: 'graphiteAssistantToolGraphQL', name: string, query: string, description: string, arguments: Array<{ __typename?: 'graphiteAssistantToolArgument', name: string, type: string, description: string, required: boolean }> }> | null, webhooks?: Array<{ __typename?: 'graphiteAssistantToolWebhook', name: string, URL: string, description: string, arguments: Array<{ __typename?: 'graphiteAssistantToolArgument', name: string, type: string, description: string, required: boolean }> }> | null }> } | null };
|
||||
|
||||
export type GetGraphiteSessionsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetGraphiteSessionsQuery = { __typename?: 'query_root', graphite?: { __typename?: 'graphiteQuery', sessions: Array<{ __typename?: 'graphiteSession', sessionID: string }> } | null };
|
||||
|
||||
export type InsertAssistantMutationVariables = Exact<{
|
||||
data: GraphiteAssistantInput;
|
||||
}>;
|
||||
@@ -7451,6 +7456,45 @@ export type GetAssistantsQueryResult = Apollo.QueryResult<GetAssistantsQuery, Ge
|
||||
export function refetchGetAssistantsQuery(variables?: GetAssistantsQueryVariables) {
|
||||
return { query: GetAssistantsDocument, variables: variables }
|
||||
}
|
||||
export const GetGraphiteSessionsDocument = gql`
|
||||
query getGraphiteSessions {
|
||||
graphite {
|
||||
sessions {
|
||||
sessionID
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useGetGraphiteSessionsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetGraphiteSessionsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetGraphiteSessionsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useGetGraphiteSessionsQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetGraphiteSessionsQuery(baseOptions?: Apollo.QueryHookOptions<GetGraphiteSessionsQuery, GetGraphiteSessionsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetGraphiteSessionsQuery, GetGraphiteSessionsQueryVariables>(GetGraphiteSessionsDocument, options);
|
||||
}
|
||||
export function useGetGraphiteSessionsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetGraphiteSessionsQuery, GetGraphiteSessionsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetGraphiteSessionsQuery, GetGraphiteSessionsQueryVariables>(GetGraphiteSessionsDocument, options);
|
||||
}
|
||||
export type GetGraphiteSessionsQueryHookResult = ReturnType<typeof useGetGraphiteSessionsQuery>;
|
||||
export type GetGraphiteSessionsLazyQueryHookResult = ReturnType<typeof useGetGraphiteSessionsLazyQuery>;
|
||||
export type GetGraphiteSessionsQueryResult = Apollo.QueryResult<GetGraphiteSessionsQuery, GetGraphiteSessionsQueryVariables>;
|
||||
export function refetchGetGraphiteSessionsQuery(variables?: GetGraphiteSessionsQueryVariables) {
|
||||
return { query: GetGraphiteSessionsDocument, variables: variables }
|
||||
}
|
||||
export const InsertAssistantDocument = gql`
|
||||
mutation insertAssistant($data: graphiteAssistantInput!) {
|
||||
graphite {
|
||||
|
||||
96
dashboard/src/utils/__generated__/graphql.ts
generated
96
dashboard/src/utils/__generated__/graphql.ts
generated
@@ -3362,7 +3362,6 @@ export type Apps = {
|
||||
/** An object relationship */
|
||||
plan: Plans;
|
||||
planId: Scalars['uuid'];
|
||||
providersUpdated?: Maybe<Scalars['Boolean']>;
|
||||
/** An object relationship */
|
||||
region: Regions;
|
||||
regionId: Scalars['uuid'];
|
||||
@@ -3626,7 +3625,6 @@ export type Apps_Bool_Exp = {
|
||||
nhostBaseFolder?: InputMaybe<String_Comparison_Exp>;
|
||||
plan?: InputMaybe<Plans_Bool_Exp>;
|
||||
planId?: InputMaybe<Uuid_Comparison_Exp>;
|
||||
providersUpdated?: InputMaybe<Boolean_Comparison_Exp>;
|
||||
region?: InputMaybe<Regions_Bool_Exp>;
|
||||
regionId?: InputMaybe<Uuid_Comparison_Exp>;
|
||||
repositoryProductionBranch?: InputMaybe<String_Comparison_Exp>;
|
||||
@@ -3700,7 +3698,6 @@ export type Apps_Insert_Input = {
|
||||
nhostBaseFolder?: InputMaybe<Scalars['String']>;
|
||||
plan?: InputMaybe<Plans_Obj_Rel_Insert_Input>;
|
||||
planId?: InputMaybe<Scalars['uuid']>;
|
||||
providersUpdated?: InputMaybe<Scalars['Boolean']>;
|
||||
region?: InputMaybe<Regions_Obj_Rel_Insert_Input>;
|
||||
regionId?: InputMaybe<Scalars['uuid']>;
|
||||
repositoryProductionBranch?: InputMaybe<Scalars['String']>;
|
||||
@@ -3862,7 +3859,6 @@ export type Apps_Order_By = {
|
||||
nhostBaseFolder?: InputMaybe<Order_By>;
|
||||
plan?: InputMaybe<Plans_Order_By>;
|
||||
planId?: InputMaybe<Order_By>;
|
||||
providersUpdated?: InputMaybe<Order_By>;
|
||||
region?: InputMaybe<Regions_Order_By>;
|
||||
regionId?: InputMaybe<Order_By>;
|
||||
repositoryProductionBranch?: InputMaybe<Order_By>;
|
||||
@@ -3919,8 +3915,6 @@ export enum Apps_Select_Column {
|
||||
/** column name */
|
||||
PlanId = 'planId',
|
||||
/** column name */
|
||||
ProvidersUpdated = 'providersUpdated',
|
||||
/** column name */
|
||||
RegionId = 'regionId',
|
||||
/** column name */
|
||||
RepositoryProductionBranch = 'repositoryProductionBranch',
|
||||
@@ -3943,8 +3937,6 @@ export enum Apps_Select_Column_Apps_Aggregate_Bool_Exp_Bool_And_Arguments_Column
|
||||
/** column name */
|
||||
IsLocked = 'isLocked',
|
||||
/** column name */
|
||||
ProvidersUpdated = 'providersUpdated',
|
||||
/** column name */
|
||||
StateMatch = 'stateMatch'
|
||||
}
|
||||
|
||||
@@ -3953,8 +3945,6 @@ export enum Apps_Select_Column_Apps_Aggregate_Bool_Exp_Bool_Or_Arguments_Columns
|
||||
/** column name */
|
||||
IsLocked = 'isLocked',
|
||||
/** column name */
|
||||
ProvidersUpdated = 'providersUpdated',
|
||||
/** column name */
|
||||
StateMatch = 'stateMatch'
|
||||
}
|
||||
|
||||
@@ -3975,7 +3965,6 @@ export type Apps_Set_Input = {
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
nhostBaseFolder?: InputMaybe<Scalars['String']>;
|
||||
planId?: InputMaybe<Scalars['uuid']>;
|
||||
providersUpdated?: InputMaybe<Scalars['Boolean']>;
|
||||
regionId?: InputMaybe<Scalars['uuid']>;
|
||||
repositoryProductionBranch?: InputMaybe<Scalars['String']>;
|
||||
slug?: InputMaybe<Scalars['String']>;
|
||||
@@ -4049,7 +4038,6 @@ export type Apps_Stream_Cursor_Value_Input = {
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
nhostBaseFolder?: InputMaybe<Scalars['String']>;
|
||||
planId?: InputMaybe<Scalars['uuid']>;
|
||||
providersUpdated?: InputMaybe<Scalars['Boolean']>;
|
||||
regionId?: InputMaybe<Scalars['uuid']>;
|
||||
repositoryProductionBranch?: InputMaybe<Scalars['String']>;
|
||||
slug?: InputMaybe<Scalars['String']>;
|
||||
@@ -4106,8 +4094,6 @@ export enum Apps_Update_Column {
|
||||
/** column name */
|
||||
PlanId = 'planId',
|
||||
/** column name */
|
||||
ProvidersUpdated = 'providersUpdated',
|
||||
/** column name */
|
||||
RegionId = 'regionId',
|
||||
/** column name */
|
||||
RepositoryProductionBranch = 'repositoryProductionBranch',
|
||||
@@ -14962,6 +14948,7 @@ export type Plans = {
|
||||
/** An aggregate relationship */
|
||||
apps_aggregate: Apps_Aggregate;
|
||||
createdAt: Scalars['timestamptz'];
|
||||
deprecated: Scalars['Boolean'];
|
||||
featureBackupEnabled: Scalars['Boolean'];
|
||||
featureCustomDomainsEnabled: Scalars['Boolean'];
|
||||
featureCustomEmailTemplatesEnabled: Scalars['Boolean'];
|
||||
@@ -14975,6 +14962,7 @@ export type Plans = {
|
||||
/** Max number of functions to deploy per git deployment */
|
||||
featureMaxNumberOfFunctionsPerDeployment: Scalars['Int'];
|
||||
id: Scalars['uuid'];
|
||||
individual: Scalars['Boolean'];
|
||||
isDefault: Scalars['Boolean'];
|
||||
isFree: Scalars['Boolean'];
|
||||
isPublic: Scalars['Boolean'];
|
||||
@@ -15062,6 +15050,7 @@ export type Plans_Bool_Exp = {
|
||||
apps?: InputMaybe<Apps_Bool_Exp>;
|
||||
apps_aggregate?: InputMaybe<Apps_Aggregate_Bool_Exp>;
|
||||
createdAt?: InputMaybe<Timestamptz_Comparison_Exp>;
|
||||
deprecated?: InputMaybe<Boolean_Comparison_Exp>;
|
||||
featureBackupEnabled?: InputMaybe<Boolean_Comparison_Exp>;
|
||||
featureCustomDomainsEnabled?: InputMaybe<Boolean_Comparison_Exp>;
|
||||
featureCustomEmailTemplatesEnabled?: InputMaybe<Boolean_Comparison_Exp>;
|
||||
@@ -15072,6 +15061,7 @@ export type Plans_Bool_Exp = {
|
||||
featureMaxFilesSize?: InputMaybe<Int_Comparison_Exp>;
|
||||
featureMaxNumberOfFunctionsPerDeployment?: InputMaybe<Int_Comparison_Exp>;
|
||||
id?: InputMaybe<Uuid_Comparison_Exp>;
|
||||
individual?: InputMaybe<Boolean_Comparison_Exp>;
|
||||
isDefault?: InputMaybe<Boolean_Comparison_Exp>;
|
||||
isFree?: InputMaybe<Boolean_Comparison_Exp>;
|
||||
isPublic?: InputMaybe<Boolean_Comparison_Exp>;
|
||||
@@ -15110,6 +15100,7 @@ export type Plans_Inc_Input = {
|
||||
export type Plans_Insert_Input = {
|
||||
apps?: InputMaybe<Apps_Arr_Rel_Insert_Input>;
|
||||
createdAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
deprecated?: InputMaybe<Scalars['Boolean']>;
|
||||
featureBackupEnabled?: InputMaybe<Scalars['Boolean']>;
|
||||
featureCustomDomainsEnabled?: InputMaybe<Scalars['Boolean']>;
|
||||
featureCustomEmailTemplatesEnabled?: InputMaybe<Scalars['Boolean']>;
|
||||
@@ -15123,6 +15114,7 @@ export type Plans_Insert_Input = {
|
||||
/** Max number of functions to deploy per git deployment */
|
||||
featureMaxNumberOfFunctionsPerDeployment?: InputMaybe<Scalars['Int']>;
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
individual?: InputMaybe<Scalars['Boolean']>;
|
||||
isDefault?: InputMaybe<Scalars['Boolean']>;
|
||||
isFree?: InputMaybe<Scalars['Boolean']>;
|
||||
isPublic?: InputMaybe<Scalars['Boolean']>;
|
||||
@@ -15214,6 +15206,7 @@ export type Plans_On_Conflict = {
|
||||
export type Plans_Order_By = {
|
||||
apps_aggregate?: InputMaybe<Apps_Aggregate_Order_By>;
|
||||
createdAt?: InputMaybe<Order_By>;
|
||||
deprecated?: InputMaybe<Order_By>;
|
||||
featureBackupEnabled?: InputMaybe<Order_By>;
|
||||
featureCustomDomainsEnabled?: InputMaybe<Order_By>;
|
||||
featureCustomEmailTemplatesEnabled?: InputMaybe<Order_By>;
|
||||
@@ -15224,6 +15217,7 @@ export type Plans_Order_By = {
|
||||
featureMaxFilesSize?: InputMaybe<Order_By>;
|
||||
featureMaxNumberOfFunctionsPerDeployment?: InputMaybe<Order_By>;
|
||||
id?: InputMaybe<Order_By>;
|
||||
individual?: InputMaybe<Order_By>;
|
||||
isDefault?: InputMaybe<Order_By>;
|
||||
isFree?: InputMaybe<Order_By>;
|
||||
isPublic?: InputMaybe<Order_By>;
|
||||
@@ -15250,6 +15244,8 @@ export enum Plans_Select_Column {
|
||||
/** column name */
|
||||
CreatedAt = 'createdAt',
|
||||
/** column name */
|
||||
Deprecated = 'deprecated',
|
||||
/** column name */
|
||||
FeatureBackupEnabled = 'featureBackupEnabled',
|
||||
/** column name */
|
||||
FeatureCustomDomainsEnabled = 'featureCustomDomainsEnabled',
|
||||
@@ -15270,6 +15266,8 @@ export enum Plans_Select_Column {
|
||||
/** column name */
|
||||
Id = 'id',
|
||||
/** column name */
|
||||
Individual = 'individual',
|
||||
/** column name */
|
||||
IsDefault = 'isDefault',
|
||||
/** column name */
|
||||
IsFree = 'isFree',
|
||||
@@ -15302,6 +15300,7 @@ export enum Plans_Select_Column {
|
||||
/** input type for updating data in table "plans" */
|
||||
export type Plans_Set_Input = {
|
||||
createdAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
deprecated?: InputMaybe<Scalars['Boolean']>;
|
||||
featureBackupEnabled?: InputMaybe<Scalars['Boolean']>;
|
||||
featureCustomDomainsEnabled?: InputMaybe<Scalars['Boolean']>;
|
||||
featureCustomEmailTemplatesEnabled?: InputMaybe<Scalars['Boolean']>;
|
||||
@@ -15315,6 +15314,7 @@ export type Plans_Set_Input = {
|
||||
/** Max number of functions to deploy per git deployment */
|
||||
featureMaxNumberOfFunctionsPerDeployment?: InputMaybe<Scalars['Int']>;
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
individual?: InputMaybe<Scalars['Boolean']>;
|
||||
isDefault?: InputMaybe<Scalars['Boolean']>;
|
||||
isFree?: InputMaybe<Scalars['Boolean']>;
|
||||
isPublic?: InputMaybe<Scalars['Boolean']>;
|
||||
@@ -15381,6 +15381,7 @@ export type Plans_Stream_Cursor_Input = {
|
||||
/** Initial value of the column from where the streaming should start */
|
||||
export type Plans_Stream_Cursor_Value_Input = {
|
||||
createdAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
deprecated?: InputMaybe<Scalars['Boolean']>;
|
||||
featureBackupEnabled?: InputMaybe<Scalars['Boolean']>;
|
||||
featureCustomDomainsEnabled?: InputMaybe<Scalars['Boolean']>;
|
||||
featureCustomEmailTemplatesEnabled?: InputMaybe<Scalars['Boolean']>;
|
||||
@@ -15394,6 +15395,7 @@ export type Plans_Stream_Cursor_Value_Input = {
|
||||
/** Max number of functions to deploy per git deployment */
|
||||
featureMaxNumberOfFunctionsPerDeployment?: InputMaybe<Scalars['Int']>;
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
individual?: InputMaybe<Scalars['Boolean']>;
|
||||
isDefault?: InputMaybe<Scalars['Boolean']>;
|
||||
isFree?: InputMaybe<Scalars['Boolean']>;
|
||||
isPublic?: InputMaybe<Scalars['Boolean']>;
|
||||
@@ -15428,6 +15430,8 @@ export enum Plans_Update_Column {
|
||||
/** column name */
|
||||
CreatedAt = 'createdAt',
|
||||
/** column name */
|
||||
Deprecated = 'deprecated',
|
||||
/** column name */
|
||||
FeatureBackupEnabled = 'featureBackupEnabled',
|
||||
/** column name */
|
||||
FeatureCustomDomainsEnabled = 'featureCustomDomainsEnabled',
|
||||
@@ -15448,6 +15452,8 @@ export enum Plans_Update_Column {
|
||||
/** column name */
|
||||
Id = 'id',
|
||||
/** column name */
|
||||
Individual = 'individual',
|
||||
/** column name */
|
||||
IsDefault = 'isDefault',
|
||||
/** column name */
|
||||
IsFree = 'isFree',
|
||||
@@ -15714,6 +15720,12 @@ export type Query_Root = {
|
||||
getLogsVolume: Metrics;
|
||||
getPostgresVolumeCapacity: Metrics;
|
||||
getPostgresVolumeUsage: Metrics;
|
||||
/**
|
||||
* Returns list of label values for a given label within a range of time.
|
||||
*
|
||||
* If `from` and `to` are not provided, they default to 6 hour ago and now, respectively.
|
||||
*/
|
||||
getServiceLabelValues: Array<Scalars['String']>;
|
||||
getTotalRequests: Metrics;
|
||||
getUsageAll: Array<UsageSummary>;
|
||||
/** fetch data from the table: "github_app_installations" using primary key columns */
|
||||
@@ -16558,6 +16570,11 @@ export type Query_RootGetPostgresVolumeUsageArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type Query_RootGetServiceLabelValuesArgs = {
|
||||
appID: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type Query_RootGetTotalRequestsArgs = {
|
||||
appID: Scalars['String'];
|
||||
from?: InputMaybe<Scalars['Timestamp']>;
|
||||
@@ -22490,7 +22507,7 @@ export type DeleteApplicationMutation = { __typename?: 'mutation_root', deleteAp
|
||||
export type GetAllWorkspacesAndProjectsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetAllWorkspacesAndProjectsQuery = { __typename?: 'query_root', workspaces: Array<{ __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, providersUpdated?: boolean | null, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> }> };
|
||||
export type GetAllWorkspacesAndProjectsQuery = { __typename?: 'query_root', workspaces: Array<{ __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> }> };
|
||||
|
||||
export type GetAppPlanAndGlobalPlansAppFragment = { __typename?: 'apps', id: any, subdomain: string, workspace: { __typename?: 'workspaces', id: any, paymentMethods: Array<{ __typename?: 'paymentMethods', id: any }> }, plan: { __typename?: 'plans', id: any, name: string } };
|
||||
|
||||
@@ -22547,7 +22564,7 @@ export type GetWorkspaceAndProjectQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetWorkspaceAndProjectQuery = { __typename?: 'query_root', workspaces: Array<{ __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, providersUpdated?: boolean | null, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> }> };
|
||||
export type GetWorkspaceAndProjectQuery = { __typename?: 'query_root', workspaces: Array<{ __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> }> };
|
||||
|
||||
export type InsertApplicationMutationVariables = Exact<{
|
||||
app: Apps_Insert_Input;
|
||||
@@ -22739,9 +22756,9 @@ export type GetFilesAggregateQuery = { __typename?: 'query_root', filesAggregate
|
||||
|
||||
export type AppStateHistoryFragment = { __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any };
|
||||
|
||||
export type ProjectFragment = { __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, providersUpdated?: boolean | null, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null };
|
||||
export type ProjectFragment = { __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null };
|
||||
|
||||
export type WorkspaceFragment = { __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, providersUpdated?: boolean | null, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> };
|
||||
export type WorkspaceFragment = { __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> };
|
||||
|
||||
export type GithubRepositoryFragment = { __typename?: 'githubRepositories', id: any, name: string, fullName: string, private: boolean, githubAppInstallation: { __typename?: 'githubAppInstallations', id: any, accountLogin?: string | null, accountType?: string | null, accountAvatarUrl?: string | null } };
|
||||
|
||||
@@ -22979,13 +22996,6 @@ export type UpdateRunServiceConfigMutationVariables = Exact<{
|
||||
|
||||
export type UpdateRunServiceConfigMutation = { __typename?: 'mutation_root', updateRunServiceConfig: { __typename?: 'ConfigRunServiceConfig', name: any } };
|
||||
|
||||
export type ConfirmProvidersUpdatedMutationVariables = Exact<{
|
||||
id: Scalars['uuid'];
|
||||
}>;
|
||||
|
||||
|
||||
export type ConfirmProvidersUpdatedMutation = { __typename?: 'mutation_root', updateApp?: { __typename?: 'apps', id: any } | null };
|
||||
|
||||
export type GetFreeAndActiveProjectsQueryVariables = Exact<{
|
||||
userId: Scalars['uuid'];
|
||||
}>;
|
||||
@@ -23240,7 +23250,6 @@ export const ProjectFragmentDoc = gql`
|
||||
createdAt
|
||||
desiredState
|
||||
nhostBaseFolder
|
||||
providersUpdated
|
||||
config(resolve: true) {
|
||||
observability {
|
||||
grafana {
|
||||
@@ -24487,7 +24496,7 @@ export const PrefetchNewAppDocument = gql`
|
||||
regions(order_by: {city: asc}) {
|
||||
...PrefetchNewAppRegions
|
||||
}
|
||||
plans(order_by: {sort: asc}) {
|
||||
plans(order_by: {sort: asc}, where: {deprecated: {_eq: false}}) {
|
||||
...PrefetchNewAppPlans
|
||||
}
|
||||
workspaces {
|
||||
@@ -26751,39 +26760,6 @@ export function useUpdateRunServiceConfigMutation(baseOptions?: Apollo.MutationH
|
||||
export type UpdateRunServiceConfigMutationHookResult = ReturnType<typeof useUpdateRunServiceConfigMutation>;
|
||||
export type UpdateRunServiceConfigMutationResult = Apollo.MutationResult<UpdateRunServiceConfigMutation>;
|
||||
export type UpdateRunServiceConfigMutationOptions = Apollo.BaseMutationOptions<UpdateRunServiceConfigMutation, UpdateRunServiceConfigMutationVariables>;
|
||||
export const ConfirmProvidersUpdatedDocument = gql`
|
||||
mutation confirmProvidersUpdated($id: uuid!) {
|
||||
updateApp(pk_columns: {id: $id}, _set: {providersUpdated: true}) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type ConfirmProvidersUpdatedMutationFn = Apollo.MutationFunction<ConfirmProvidersUpdatedMutation, ConfirmProvidersUpdatedMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useConfirmProvidersUpdatedMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useConfirmProvidersUpdatedMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useConfirmProvidersUpdatedMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [confirmProvidersUpdatedMutation, { data, loading, error }] = useConfirmProvidersUpdatedMutation({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useConfirmProvidersUpdatedMutation(baseOptions?: Apollo.MutationHookOptions<ConfirmProvidersUpdatedMutation, ConfirmProvidersUpdatedMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<ConfirmProvidersUpdatedMutation, ConfirmProvidersUpdatedMutationVariables>(ConfirmProvidersUpdatedDocument, options);
|
||||
}
|
||||
export type ConfirmProvidersUpdatedMutationHookResult = ReturnType<typeof useConfirmProvidersUpdatedMutation>;
|
||||
export type ConfirmProvidersUpdatedMutationResult = Apollo.MutationResult<ConfirmProvidersUpdatedMutation>;
|
||||
export type ConfirmProvidersUpdatedMutationOptions = Apollo.BaseMutationOptions<ConfirmProvidersUpdatedMutation, ConfirmProvidersUpdatedMutationVariables>;
|
||||
export const GetFreeAndActiveProjectsDocument = gql`
|
||||
query GetFreeAndActiveProjects($userId: uuid!) {
|
||||
freeAndActiveProjects: apps(
|
||||
|
||||
3
docs/.gitignore
vendored
3
docs/.gitignore
vendored
@@ -5,7 +5,6 @@
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
@@ -18,5 +17,3 @@
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.vercel
|
||||
docs/reference/docgen
|
||||
@@ -1,5 +1,23 @@
|
||||
# @nhost/docs
|
||||
|
||||
## 2.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 65b6a48d5: feat: added graphite/cli documentation
|
||||
|
||||
## 2.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- 6d08b3430: New Docs powered by Mintlify
|
||||
|
||||
## 0.7.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 7c977e714: chore: change `Allowed Roles` to `Default Allowed Roles`
|
||||
|
||||
## 0.7.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,37 +1,32 @@
|
||||
# Nhost Docs
|
||||
# Mintlify Starter Kit
|
||||
|
||||
This documentation describes how to build, start and test the documentation locally.
|
||||
Click on `Use this template` to copy the Mintlify starter kit. The starter kit contains examples including
|
||||
|
||||
### Installation
|
||||
- Guide pages
|
||||
- Navigation
|
||||
- Customizations
|
||||
- API Reference pages
|
||||
- Use of popular components
|
||||
|
||||
```bash
|
||||
$ pnpm i
|
||||
### Development
|
||||
|
||||
Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command
|
||||
|
||||
```
|
||||
npm i -g mintlify
|
||||
```
|
||||
|
||||
### Local Development
|
||||
Run the following command at the root of your documentation (where mint.json is)
|
||||
|
||||
```bash
|
||||
$ pnpm start
|
||||
```
|
||||
mintlify dev
|
||||
```
|
||||
|
||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||
### Publishing Changes
|
||||
|
||||
### Build
|
||||
Install our Github App to autopropagate changes from youre repo to your deployment. Changes will be deployed to production automatically after pushing to the default branch. Find the link to install on your dashboard.
|
||||
|
||||
```bash
|
||||
$ pnpm build
|
||||
```
|
||||
#### Troubleshooting
|
||||
|
||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||
|
||||
### Serve
|
||||
|
||||
```bash
|
||||
$ pnpm serve
|
||||
```
|
||||
|
||||
This command serves the static content from the `build` directory.
|
||||
|
||||
### Contributing
|
||||
|
||||
All pull requests are greatly appreciated! See our [contributing guide](https://github.com/nhost/nhost/blob/main/CONTRIBUTING.md) to get started.
|
||||
- Mintlify dev isn't running - Run `mintlify install` it'll re-install dependencies.
|
||||
- Page loads as a 404 - Make sure you are running in a folder with `mint.json`
|
||||
|
||||
3
docs/_snippets/snippet-example.mdx
Normal file
3
docs/_snippets/snippet-example.mdx
Normal file
@@ -0,0 +1,3 @@
|
||||
## My Snippet
|
||||
|
||||
<Info>This is an example of a reusable snippet</Info>
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
|
||||
};
|
||||
162
docs/development/cli/commands.mdx
Normal file
162
docs/development/cli/commands.mdx
Normal file
@@ -0,0 +1,162 @@
|
||||
---
|
||||
title: Commands
|
||||
description: Available commands to run and manage local Nhost projects
|
||||
icon: rectangle-code
|
||||
---
|
||||
|
||||
## `nhost init`
|
||||
|
||||
Initializes current directory with a new project containing configuration and objects necessary to run Nhost locally.
|
||||
|
||||
<Accordion title="Output">
|
||||
|
||||
Initializing Nhost project
|
||||
|
||||
Successfully initialized Nhost project, run `nhost up` to start development
|
||||
</Accordion>
|
||||
|
||||
### Flags
|
||||
|
||||
`--remote` optional `false` <br />
|
||||
Pulls state from a remote Nhost instance.
|
||||
|
||||
|
||||
## `nhost up`
|
||||
|
||||
Starts Nhost for development and testing purposes. Requires scaffolding from `nhost init`.
|
||||
|
||||
<Accordion title="Output">
|
||||
|
||||
```
|
||||
Nhost development environment started.
|
||||
URLs:
|
||||
- Postgres: postgres://postgres:postgres@localhost:5432/local
|
||||
- Hasura: https://local.hasura.nhost.run
|
||||
- GraphQL: https://local.graphql.nhost.run
|
||||
- Auth: https://local.auth.nhost.run
|
||||
- Storage: https://local.storage.nhost.run
|
||||
- Functions: https://local.functions.nhost.run
|
||||
- Dashboard: https://local.dashboard.nhost.run
|
||||
- Mailhog: https://local.mailhog.nhost.run
|
||||
|
||||
SDK Configuration:
|
||||
Subdomain: local
|
||||
Region: (empty)
|
||||
|
||||
Run `nhost up` to reload the development environment
|
||||
Run `nhost down` to stop the development environment
|
||||
Run `nhost logs` to watch the logs
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
|
||||
|
||||
## `nhost down`
|
||||
|
||||
Stops all services and deletes all containers.
|
||||
|
||||
### Flags
|
||||
|
||||
`--volumes` optional `false` <br />
|
||||
Remove volumes.
|
||||
|
||||
|
||||
## `nhost login`
|
||||
|
||||
Login to an Nhost account to perform authenticated operations.
|
||||
|
||||
<Accordion title="Output">
|
||||
- email: user@domain.com
|
||||
- password:
|
||||
|
||||
Authenticating <br />
|
||||
Successfully logged in, creating PAT <br />
|
||||
Successfully created PAT <br />
|
||||
Storing PAT for future user
|
||||
|
||||
</Accordion>
|
||||
|
||||
### Flags
|
||||
|
||||
`--email` optional <br />
|
||||
Email adress.
|
||||
|
||||
`--password` optional <br />
|
||||
Password.
|
||||
|
||||
`--pat` optional <br />
|
||||
Use this Personal Access Token instead of generating a new one with email/password.
|
||||
|
||||
## `nhost logs`
|
||||
|
||||
Render all logs.
|
||||
|
||||
<Accordion title="Output">
|
||||
```
|
||||
app-auth-1 | {"level":"info","message":"Applying SQL migrations..."}
|
||||
app-auth-1 | {"level":"info","message":"SQL migrations applied"}
|
||||
app-auth-1 | {"level":"info","message":"Applying metadata..."}
|
||||
app-auth-1 | {"level":"info","message":"Metadata applied"}
|
||||
app-auth-1 | {"level":"info","message":"Running on port 4000"}
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
### Flags
|
||||
|
||||
`--follow` optional <br />
|
||||
Follow (or tail) log output.
|
||||
|
||||
`--no-color` optional <br />
|
||||
Produce monochrome output.
|
||||
|
||||
`--no-log-prefix` optional <br />
|
||||
Don't print prefix in logs.
|
||||
|
||||
`--since` optional <br />
|
||||
Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)
|
||||
|
||||
`--tail` optional `all`<br />
|
||||
Number of lines to show from the end of the logs for each service
|
||||
|
||||
`--timestamps` optional <br />
|
||||
Show timestamps
|
||||
|
||||
`--until` optional <br />
|
||||
Show logs before a timestamp
|
||||
|
||||
## `nhost list`
|
||||
|
||||
List all remote Nhost projects this user has access to.
|
||||
|
||||
<Accordion title="Output">
|
||||
|
||||
```
|
||||
# │ Subdomain │ Project │ Workspace │ Region │
|
||||
1 │ dahvgwlspuridghplrso │ nbp │ Nuno's Workspace │ eu-central-1 │
|
||||
2 │ mhmvhergiiycqfvhisan │ Bun Generator │ Nhost Examples │ eu-central-1 │
|
||||
4 │ qaxzubvwbuhzgxswghug │ Next.js Stripe Starter Template │ Nhost Examples │ eu-central-1 │
|
||||
5 │ cuzwcdqwgmhbxqetfbci │ Nhost Netlify Starter Template │ Nhost Examples │ us-east-1 │
|
||||
6 │ jsjiiuwiuqdvvwgwhxnx │ quickstarts │ Nhost Examples │ eu-central-1 │
|
||||
7 │ vvhjnsgebtspueuuxnvp │ React Apollo │ Nhost Examples │ eu-central-1 │
|
||||
9 │ vue-quickstart │ vue-quickstart │ Nhost Examples │ eu-central-1 │
|
||||
11 │ odqlmnqxospbvqvphuyl │ monitoring-app-frankfurt │ monitoring │ eu-central-1 │
|
||||
12 │ xmhqdbhkvskuubnelgkc │ monitoring-app-london │ monitoring │ eu-west-2 │
|
||||
13 │ jjetetkbmovfgyewremk │ monitoring-app-mumbai │ monitoring │ ap-south-1 │
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
## `nhost secrets`
|
||||
|
||||
Manage secrets in the Nhost cloud.
|
||||
|
||||
### `nhost secrets create`
|
||||
|
||||
#### Flags
|
||||
|
||||
<Accordion title="Output">
|
||||
|
||||
</Accordion>
|
||||
|
||||
87
docs/development/cli/getting-started.mdx
Normal file
87
docs/development/cli/getting-started.mdx
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
title: Nhost CLI
|
||||
description: Tools to develop and test Nhost projects locally
|
||||
icon: square-terminal
|
||||
---
|
||||
|
||||
## Instaling the Nhost CLI
|
||||
|
||||
To install the Nhost CLI copy the following command and paste it into your terminal:
|
||||
|
||||
```bash
|
||||
> sudo curl -L https://raw.githubusercontent.com/nhost/cli/main/get.sh | bash
|
||||
```
|
||||
|
||||
The `get.sh` script checks for both the architecture and operating system and installs the right binary.
|
||||
|
||||
### Supported Platforms:
|
||||
|
||||
- MacOS
|
||||
- Linux
|
||||
- Windows WSL2
|
||||
|
||||
### Dependencies:
|
||||
|
||||
- [Docker](https://docs.docker.com/get-docker/)
|
||||
- [curl](https://curl.se/)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
|
||||
## Updating the Nhost CLI
|
||||
|
||||
Update an existing installation to the latest version.
|
||||
|
||||
```bash Terminal
|
||||
> nhost sw upgrade
|
||||
```
|
||||
|
||||
## Running Nhost
|
||||
|
||||
Inside of the folder where you want to create your project, run:
|
||||
|
||||
```bash Terminal
|
||||
> nhost init
|
||||
|
||||
Initializing Nhost project
|
||||
Successfully initialized Nhost project, run `nhost up` to start development
|
||||
```
|
||||
|
||||
`init` will scaffold a bunch of files that Nhost uses to manage configuration, database schema and migrations, APIs, functions, etc.
|
||||
|
||||
With the project scaffolding in place, start the local Nhost instance with `nhost up`:
|
||||
|
||||
```bash Terminal
|
||||
> nhost up
|
||||
|
||||
|
||||
Nhost development environment started.
|
||||
URLs:
|
||||
- Postgres: postgres://postgres:postgres@localhost:5432/local
|
||||
- Hasura: https://local.hasura.nhost.run
|
||||
- GraphQL: https://local.graphql.nhost.run
|
||||
- Auth: https://local.auth.nhost.run
|
||||
- Storage: https://local.storage.nhost.run
|
||||
- Functions: https://local.functions.nhost.run
|
||||
- Dashboard: https://local.dashboard.nhost.run
|
||||
- Mailhog: https://local.mailhog.nhost.run
|
||||
|
||||
SDK Configuration:
|
||||
Subdomain: local
|
||||
Region: (empty)
|
||||
|
||||
Run `nhost up` to reload the development environment
|
||||
Run `nhost down` to stop the development environment
|
||||
Run `nhost logs` to watch the logs
|
||||
```
|
||||
|
||||
The following services are started:
|
||||
|
||||
- Postgres
|
||||
- Hasura Console
|
||||
- GraphQL API
|
||||
- Authentication
|
||||
- Storage
|
||||
- Functions
|
||||
- Dashboard
|
||||
- Mailhog (SMTP server for testing emails locally)
|
||||
|
||||
<Info>Support for Nhost Run services is coming soon</Info>
|
||||
32
docs/development/cli/overview.mdx
Normal file
32
docs/development/cli/overview.mdx
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: 'Overview'
|
||||
description: 'Developing locally with the Nhost CLI'
|
||||
icon: hand-wave
|
||||
---
|
||||
|
||||
Run the Nhost Stack on your computer with `nhost init` and `nhost start`. The Nhost CLI ships with all the tools you need to run, test, and operate your projects.
|
||||
|
||||
Manage configuration, database migrations, API schema changes, and more, with ease.
|
||||
|
||||
```bash
|
||||
> nhost up
|
||||
|
||||
Nhost development environment started.
|
||||
URLs:
|
||||
- Postgres: postgres://postgres:postgres@localhost:5432/local
|
||||
- Hasura: https://local.hasura.nhost.run
|
||||
- GraphQL: https://local.graphql.nhost.run
|
||||
- Auth: https://local.auth.nhost.run
|
||||
- Storage: https://local.storage.nhost.run
|
||||
- Functions: https://local.functions.nhost.run
|
||||
- Dashboard: https://local.dashboard.nhost.run
|
||||
- Mailhog: https://local.mailhog.nhost.run
|
||||
|
||||
SDK Configuration:
|
||||
Subdomain: local
|
||||
Region: (empty)
|
||||
|
||||
Run `nhost up` to reload the development environment
|
||||
Run `nhost down` to stop the development environment
|
||||
Run `nhost logs` to watch the logs
|
||||
```
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"label": "Authentication",
|
||||
"position": 6
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
---
|
||||
title: Email Templates
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
Nhost Auth sends out transactional emails as part of the authentication service. These emails can be modified using email templates.
|
||||
|
||||
The following email templates are available:
|
||||
|
||||
- **email-verify** - Verify email address
|
||||
- **email-confirm-change** - Confirm email change to a new email address.
|
||||
- **signin-passwordless** - Magic Link
|
||||
- **password-reset** - Reset password
|
||||
|
||||
Changing email templates is only available for projects on the [Pro and Enterprise plan](https://nhost.io/pricing).
|
||||
|
||||
## Update Email Templates
|
||||
|
||||
Your project must be connected to a [Git Repository](/platform/git) to be able to change the email templates.
|
||||
|
||||
Email templates are automatically deployed during a deployment, just like database migrations, Hasura metadata, and Serverless Functions.
|
||||
|
||||
## File Structure
|
||||
|
||||
Emails are located in the `nhost/` folder like this:
|
||||
|
||||
The email templates should be provided as body.html and subject.txt files in this predefined folder structure.
|
||||
|
||||
**Example:** Email templates for `en` (English) and `es` (Spanish):
|
||||
|
||||
```txt
|
||||
my-nhost-project/
|
||||
└── nhost/
|
||||
├── config.yaml
|
||||
├── emails/
|
||||
│ ├── en/
|
||||
│ │ ├── email-verify/
|
||||
│ │ │ ├── subject.txt
|
||||
│ │ │ └── body.html
|
||||
│ │ ├── email-confirm-change/
|
||||
│ │ │ ├── subject.txt
|
||||
│ │ │ └── body.html
|
||||
│ │ ├── signin-passwordless/
|
||||
│ │ │ ├── subject.txt
|
||||
│ │ │ └── body.html
|
||||
│ │ └── password-reset/
|
||||
│ │ ├── subject.txt
|
||||
│ │ └── body.html
|
||||
│ └── es/
|
||||
│ ├── email-verify/
|
||||
│ │ ├── subject.txt
|
||||
│ │ └── body.html
|
||||
│ ├── email-confirm-change/
|
||||
│ │ ├── subject.txt
|
||||
│ │ └── body.html
|
||||
│ ├── signin-passwordless/
|
||||
│ │ ├── subject.txt
|
||||
│ │ └── body.html
|
||||
│ └── password-reset/
|
||||
│ ├── subject.txt
|
||||
│ └── body.html
|
||||
├── migrations/
|
||||
├── metadata/
|
||||
└── seeds
|
||||
```
|
||||
|
||||
As you see, the format is:
|
||||
|
||||
```
|
||||
nhost/emails/{two-letter-language-code}/{email-template}/[subject.txt, body.html]
|
||||
```
|
||||
|
||||
## Languages
|
||||
|
||||
The user's language is what decides what template to send. The user's language is stored in the `auth.users` table in the `locale` column. This `locale` column contains a two-letter language code in [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) format.
|
||||
|
||||
This value is `en` by default for new users.
|
||||
|
||||
## Variables
|
||||
|
||||
The following variables are available to use in the email templates:
|
||||
|
||||
| Variable | Description |
|
||||
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| link | The full URL to the target of the transaction. This should be used in the main call to action. This is available in all templates. |
|
||||
| serverUrl | URL of the authentication server |
|
||||
| clientUrl | URL to your frontend app |
|
||||
| redirectTo | URL where the user will be redirected to after clicking the link and finishing the action of the email |
|
||||
| ticket | Ticket that is used to authorize the link request |
|
||||
| displayName | The display name of the user. |
|
||||
| email | The email of the user. |
|
||||
| locale | Locale of the user as a two-letter language code. E.g. "en". |
|
||||
|
||||
Use variables like this: `${displayName}` in the email templates.
|
||||
|
||||
**Example:** A email template to verify users' emails:
|
||||
|
||||
```html title="nhost/emails/en/email-verify/body.html"
|
||||
<h2>Verify You Email</h2>
|
||||
|
||||
<p>Hi, ${displayName}! Please click the link to verify your email:</p>
|
||||
|
||||
<p>
|
||||
<a href="${link}">Verify Email</a>
|
||||
</p>
|
||||
```
|
||||
@@ -1,86 +0,0 @@
|
||||
---
|
||||
title: Authentication
|
||||
sidebar_label: Overview
|
||||
sidebar_position: 1
|
||||
image: /img/og/authentication.png
|
||||
---
|
||||
|
||||
Nhost Authentication is a ready-to-use authentication service that is integrated with the [GraphQL API](/graphql) and its permission system from Hasura. This allows you to easily add user authentication to your application without having to build and maintain your own authentication system.
|
||||
|
||||
Nhost Authentication lets you authenticate users using different sign-in methods:
|
||||
|
||||
- [Email and Password](/authentication/sign-in-with-email-and-password)
|
||||
- [Magic Link](/authentication/sign-in-with-magic-link)
|
||||
- [Phone Number (SMS)](/authentication/sign-in-with-phone-number-sms)
|
||||
- [Security Keys (WebAuthn)](/authentication/sign-in-with-security-keys)
|
||||
- [Apple](/authentication/sign-in-with-apple)
|
||||
- [Discord](/authentication/sign-in-with-discord)
|
||||
- [Facebook](/authentication/sign-in-with-facebook)
|
||||
- [GitHub](/authentication/sign-in-with-github)
|
||||
- [Google](/authentication/sign-in-with-google)
|
||||
- [LinkedIn](/authentication/sign-in-with-linkedin)
|
||||
- [Spotify](/authentication/sign-in-with-spotify)
|
||||
- [Twitch](/authentication/sign-in-with-twitch)
|
||||
- [WorkOS](/authentication/sign-in-with-workos)
|
||||
|
||||
## Client URL
|
||||
|
||||
Client URL is the URL of your frontend application. The Client URL is used to redirect the user after interacting with any authentication operation, like signing in or resetting their password.
|
||||
|
||||
## Allowed Redirect URLs
|
||||
|
||||
Allowed Redirect URLs are the URLs of your frontend application that are allowed to redirect the user after interacting with any authentication operation, like signing in or resetting their password. This is useful if you have multiple frontend applications that are using the same Nhost backend or if you want to redirect the user to a specific URL after interacting with an authentication operation.
|
||||
|
||||
As an example, for a staging project, you can set the Client URL to `https://staging.example.com` and Allowed Redirect URLs to `https://*.vercel.app`. This way, the user can be redirected to any Vercel deployment of your frontend application.
|
||||
|
||||
## Allowed Emails and Domains
|
||||
|
||||
Allowed Emails and Domains are used to restrict the sign-up an sign-in process to specific email addresses and domains.
|
||||
|
||||
If both allowed emails and allowed domains are set a user can only sign up if their email address matches one of the allowed emails or one of the allowed domains.
|
||||
|
||||
## Blocked Emails and Domains
|
||||
|
||||
Blocked Emails and Domains are used to block specific email addresses and domains from signing up and singin in.
|
||||
|
||||
Note that even if a user's email address matches any allowed email or domain, they will still be blocked if their email address matches any blocked email or domain.
|
||||
|
||||
## Multi-factor Authentication
|
||||
|
||||
By enabling Multi-factor Authentication (MFA), you can allow users to verify their identity using a second factor during the sign-in process. We currently support Authenticator Apps (TOTP) for MFA.
|
||||
|
||||
Once MFA is enabled, a user can enable MFA for their account by scanning a QR code with their Authenticator App. After that, they will be prompted to enter a code generated by their Authenticator App during the sign-in process.
|
||||
|
||||
We'll be adding more support in our SDKs and documentation around MFA soon.
|
||||
|
||||
## Gravatar
|
||||
|
||||
If Gravatar is enabled, Nhost Auth will use the user's email address to fetch their Gravatar profile picture. If the user doesn't have a Gravatar profile picture, a default image will be used.
|
||||
|
||||
There are two options for Gravatars:
|
||||
|
||||
### Default Image
|
||||
|
||||
If the user doesn't have a Gravatar profile picture, a default image will be used. You can choose between the following options:
|
||||
|
||||
- `404`: Do not load any image if none is associated with the email hash, instead return an HTTP 404 (File Not Found) response.
|
||||
- `mp`: (mystery-person) a simple, cartoon-style silhouetted outline of a person (does not vary by email hash).
|
||||
- `identicon`: a geometric pattern based on an email hash.
|
||||
- `monsterid`: a generated 'monster' with different colors, faces, etc.
|
||||
- `wavatar`: generated faces with differing features and backgrounds.
|
||||
- `retro`: awesome generated, 8-bit arcade-style pixelated faces.
|
||||
- `robohash`: a generated robot with different colors, faces, etc.
|
||||
- `blank`: a transparent PNG image.
|
||||
|
||||
### Rating
|
||||
|
||||
Gravatar images are rated by default. You can choose between the following options:
|
||||
|
||||
- `g`: suitable for display on all websites with any audience type.
|
||||
- `pg`: may contain rude gestures, provocatively dressed individuals, lesser swear words or mild violence.
|
||||
- `r`: may contain such things as harsh profanity, intense violence, nudity, or hard drug use.
|
||||
- `x`: may contain hardcore sexual imagery or extremely disturbing violence.
|
||||
|
||||
## Disable New Users
|
||||
|
||||
If set, newly registered users are disabled and won't be able to sign in. This is useful if you want to manually approve new users before they can sign in.
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
title: Sign In with Email and Password
|
||||
sidebar_label: Email and Password
|
||||
slug: /authentication/sign-in-with-email-and-password
|
||||
image: /img/og/sign-in-with-email-and-password.png
|
||||
---
|
||||
|
||||
The Email and Password sign-in method is always enabled for all Nhost projects.
|
||||
|
||||
## Sign Up
|
||||
|
||||
Users must first sign up to be able to sign in.
|
||||
|
||||
**Example:** Sign up users using the [Nhost JavaScript client](/reference/javascript).
|
||||
|
||||
```js
|
||||
await nhost.auth.signUp({
|
||||
email: 'joe@example.com',
|
||||
password: 'secret-password'
|
||||
})
|
||||
```
|
||||
|
||||
If you've turned on email verification in your project's **Authentication Settings**, a user will be sent a verification email upon signup. The user must click the verification link in the email before they can sign in.
|
||||
|
||||
## Sign In
|
||||
|
||||
After the user has successfully signed up, they can sign in.
|
||||
|
||||
**Example:** Sign in users using the [Nhost JavaScript client](/reference/javascript).
|
||||
|
||||
```js
|
||||
await nhost.auth.signIn({
|
||||
email: 'joe@example.com',
|
||||
password: 'secret-password'
|
||||
})
|
||||
```
|
||||
|
||||
## Email Verification
|
||||
|
||||
If you want to require users to verify their email before they can sign in, you can enable this under **Settings -> Sign-In Methods -> Email and Password** by checking the **Require Verified Emails** checkbox.
|
||||
|
||||
If **Require Verified Emails** is enabled, users automatically get a verification email when they sign up. The user must click the verification link in the email before they can sign in. It's possible to edit the ["email-verify" email template](/authentication/email-templates).
|
||||
|
||||
It's possible to manually send a verification email to the user using [`nhost.auth.sendVerificationEmail()`](/reference/javascript/auth/send-verification-email).
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
title: Sign In with Magic Link
|
||||
sidebar_label: Magic Link
|
||||
slug: /authentication/sign-in-with-magic-link
|
||||
image: /img/og/sign-in-with-magic-link.png
|
||||
---
|
||||
|
||||
Nhost allows you to sign in users with a Magic Link, which is a way to sign in users so they don't have to remember a password.
|
||||
|
||||
When users sign in using this sign-in method, they'll enter their email address and then receive an email with a (magic) link. When the user clicks on the (magic) link, they get automatically signed in to your app.
|
||||
|
||||
The sign-in method is called Magic Link because the user gets "magically" signed in without having to enter a password.
|
||||
|
||||
## Configuration
|
||||
|
||||
Enable the Magic Link sign-in method in the Nhost dashboard under **Settings -> Sign-In Methods -> Magic Link**.
|
||||
|
||||
## Sign In
|
||||
|
||||
To sign in users with Magic Link is a two-step process:
|
||||
|
||||
1. Send a Magic Link to the user's email address.
|
||||
2. The user clicks the Magic Link in their email to sign in.
|
||||
|
||||
Use the [Nhost JavaScript client](/reference/javascript) to sign in users with Magic Link:
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
email: 'joe@example.com'
|
||||
})
|
||||
```
|
||||
|
||||
There is no sign up method for Magic Link. Users will be automatically created when they sign in for the first time.
|
||||
|
||||
Users who have signed up with email and password can also sign in with Magic Link.
|
||||
|
||||
## Email
|
||||
|
||||
It's possible to edit the ["signin-passwordless" email template](/authentication/email-templates).
|
||||
@@ -1,82 +0,0 @@
|
||||
---
|
||||
title: Sign In with Personal Access Tokens
|
||||
sidebar_label: Personal Access Tokens
|
||||
slug: /authentication/sign-in-with-personal-access-tokens
|
||||
---
|
||||
|
||||
Nhost allows you to sign in users with personal access tokens (PAT) which is a way to sign in users without an email address or password.
|
||||
|
||||
## Configuration
|
||||
|
||||
:::info
|
||||
Personal Access Tokens can only be created through Hasura Auth or the [Nhost JavaScript SDK](/reference/javascript) at the moment.
|
||||
:::
|
||||
|
||||
## Create a Personal Access Token
|
||||
|
||||
Users must be signed in to create a personal access token. Once a user is signed in, they can create a personal access token.
|
||||
|
||||
**Example:** Create a personal access token:
|
||||
|
||||
```tsx
|
||||
const expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30) // 30 days
|
||||
const metadata = { name: 'Example PAT' } // Optional metadata
|
||||
|
||||
const { data, error } = await nhost.auth.createPAT(expiresAt, metadata)
|
||||
|
||||
// Something unexpected happened
|
||||
if (error) {
|
||||
console.error(error)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(data.id) // The personal access token ID (can be used to delete the token later)
|
||||
console.log(data.personalAccessToken) // The personal access token
|
||||
```
|
||||
|
||||
Users can create multiple personal access tokens. Each token can have a different expiration date and metadata.
|
||||
|
||||
## Sign In
|
||||
|
||||
Once a user has created a personal access token, they can use it to sign in.
|
||||
|
||||
**Example:** Sign in with a personal access token:
|
||||
|
||||
```tsx
|
||||
const { error, session } = await nhost.auth.signInPAT('<personal-access-token>')
|
||||
|
||||
// Something unexpected happened
|
||||
if (error) {
|
||||
console.log(error)
|
||||
return
|
||||
}
|
||||
|
||||
// User is signed in
|
||||
console.log(session.user)
|
||||
```
|
||||
|
||||
## List or Remove Personal Access Tokens
|
||||
|
||||
To list and remove personal access tokens, use GraphQL and set permissions on the `auth.refresh_tokens` table:
|
||||
|
||||
**Example:** Get all personal access tokens for a user:
|
||||
|
||||
```graphql
|
||||
query personalAccessTokens($userId: uuid!) {
|
||||
authRefreshTokens(where: { _and: [{ userId: { _eq: $userId } }, { type: { _eq: pat } }] }) {
|
||||
id
|
||||
expiresAt
|
||||
metadata
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example:** Remove a personal access token:
|
||||
|
||||
```graphql
|
||||
mutation removePersonalAccessToken($id: uuid!) {
|
||||
deleteAuthRefreshToken(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
title: Sign In with Phone Number (SMS)
|
||||
sidebar_label: Phone Number (SMS)
|
||||
slug: /authentication/sign-in-with-phone-number-sms
|
||||
image: /img/og/sign-in-with-phone-number-sms.png
|
||||
---
|
||||
|
||||
Follow this guide to sign in users with a phone number (SMS).
|
||||
|
||||
## Configuration
|
||||
|
||||
You need a [Twilio account](https://www.twilio.com/try-twilio) to use this feature because all SMS are sent through Twilio.
|
||||
|
||||
Enable the Phone Number (SMS) sign-in method in the Nhost dashboard under **Settings -> Sign-In Methods -> Phone Number (SMS)**.
|
||||
|
||||
You need to insert the following settings in the Nhost dashboard from Twilio:
|
||||
|
||||
- Account SID
|
||||
- Auth Token
|
||||
- Messaging Service SID (or a Twilio phone number)
|
||||
|
||||
## Sign In
|
||||
|
||||
To sign in users with a phone number is a two-step process:
|
||||
|
||||
1. Send a one-time password (OTP) to the user's phone number.
|
||||
2. The user uses the OTP to sign in.
|
||||
|
||||
```js
|
||||
// Step 1: Send OTP to the user's phone number
|
||||
await nhost.auth.signIn({
|
||||
phoneNumber: '+11233213123'
|
||||
})
|
||||
|
||||
// Step 2: Sign in user using their phone number and OTP
|
||||
await nhost.auth.signIn({
|
||||
phoneNumber: '+11233213123'
|
||||
// highlight-next-line
|
||||
otp: '123456',
|
||||
})
|
||||
```
|
||||
|
||||
The first time a user signs in using a phone number, the user is created. That means you don't need to sign up users before signing in users.
|
||||
|
||||
:::info
|
||||
|
||||
Phone numbers should start with `+` (not `00`) to follow the [E.164 formatting standard](https://en.wikipedia.org/wiki/E.164).
|
||||
|
||||
:::
|
||||
|
||||
## Other SMS Providers
|
||||
|
||||
We only support Twilio for now. If you want support for another SMS provider, please create an issue on [GitHub](https://github.com/nhost/nhost).
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"label": "Sign-In Methods",
|
||||
"position": 2
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
title: 'Sign-In Methods'
|
||||
slug: /authentication/sign-in-methods
|
||||
image: /img/og/sign-in-methods.png
|
||||
---
|
||||
|
||||
Nhost Authentication supports the following sign-in methods:
|
||||
|
||||
- [Email and Password](/authentication/sign-in-with-email-and-password)
|
||||
- [Magic Link](/authentication/sign-in-with-magic-link)
|
||||
- [Phone Number (SMS)](/authentication/sign-in-with-phone-number-sms)
|
||||
- [Security Keys (WebAuthn)](/authentication/sign-in-with-security-keys)
|
||||
- [Apple](/authentication/sign-in-with-apple)
|
||||
- [Discord](/authentication/sign-in-with-discord)
|
||||
- [Facebook](/authentication/sign-in-with-facebook)
|
||||
- [GitHub](/authentication/sign-in-with-github)
|
||||
- [Google](/authentication/sign-in-with-google)
|
||||
- [LinkedIn](/authentication/sign-in-with-linkedin)
|
||||
- [Spotify](/authentication/sign-in-with-spotify)
|
||||
- [Twitch](/authentication/sign-in-with-twitch)
|
||||
|
||||
## Enabling sign-in methods during local development
|
||||
|
||||
To enable a sign-in method locally, add variables corresponding to the relevant authentication methods in an `.env.development` file located in the project repository. An overview of available options is available in the [Hasura Auth repository](https://github.com/nhost/hasura-auth/blob/main/docs/environment-variables.md#oauth-environment-variables).
|
||||
@@ -1,46 +0,0 @@
|
||||
---
|
||||
title: Social Providers
|
||||
sidebar_label: Social Providers
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
## Enabling Social Sign-In Provider
|
||||
|
||||
To start with social sign-in, select your project in Nhost Dashboard and go to **Settings -> Sign-In Methods**.
|
||||
|
||||
## Implementing sign-in experience
|
||||
|
||||
Use the [Nhost JavaScript SDK](/reference/javascript) and the `signIn()` method to implement social sign-in for your project.
|
||||
|
||||
**Example**: Sign in a user with [GitHub](/authentication/sign-in-with-github).
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'github'
|
||||
})
|
||||
```
|
||||
|
||||
During the sign-in flow, the user is redirected to the provider's website to authenticate. After the user authenticates, they are redirected back to your Nhost project's [**Client URL**](/authentication#client-url) by default. You can change where the user gets redirected to after authentication by passing the `redirectTo` option.
|
||||
|
||||
**Example:** Redirect the user to `https://staging.example.com/welcome` after they complete the sign-in flow.
|
||||
|
||||
```js
|
||||
nhost.auth.signIn({
|
||||
provider: 'github'
|
||||
options: {
|
||||
redirectTo: "https://staging.example.com/welcome",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
In the example above, it's important to note that the `redirectTo` URL must be part of the [Allowed Redirect URLs](/authentication#allowed-redirect-urls) of your Nhost project.
|
||||
|
||||
## Provider OAuth scopes
|
||||
|
||||
Scopes are a mechanism in OAuth to allow or limit an application's access to a user's account.
|
||||
|
||||
By default, Nhost sets the scope to get the name, email, and avatar URL for each user. Editing scope is not currently supported.
|
||||
|
||||
## Provider OAuth Tokens
|
||||
|
||||
Nhost saves both access and refresh tokens for each user and provider in the `auth.user_providers` table. These tokens can be used to interact with the provider if needed.
|
||||
@@ -1,74 +0,0 @@
|
||||
---
|
||||
title: Tokens
|
||||
sidebar_label: Tokens
|
||||
sidebar_position: 10
|
||||
image: /img/og/tokens.png
|
||||
---
|
||||
|
||||
Nhost Authentication makes use of two types of tokens:
|
||||
|
||||
- **Access token** - used to authenticate a user and access APIs.
|
||||
- **Refresh token** - used to get a new access token.
|
||||
|
||||
Users get both an access token and a refresh token when they sign in.
|
||||
|
||||
:::info
|
||||
If you're using the [Nhost JavaScript client](/reference/javascript), all tokens are automatically set and updated for you. But it can still be good to understand how they work.
|
||||
:::
|
||||
|
||||
## Access Token
|
||||
|
||||
An access token (also called [JSON Web Token or JWT](https://en.wikipedia.org/wiki/JSON_Web_Token)) contains information about the user such as the user id. Users send this token to the Nhost services (GraphQL, Auth, Storage, Serverless Functions) to let the services know who's making the request so the services can verify the user's identity and resolve the correct permissions.
|
||||
|
||||
The access token is added as an `Authorization` header when making a request, like this:
|
||||
|
||||
```http title="Header"
|
||||
Authorization: Bearer <access_token>
|
||||
```
|
||||
|
||||
Here's an example of an encoded access token:
|
||||
|
||||
```
|
||||
eyJhbGciOiJIUzI1NiJ9.eyJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsibWUiLCJ1c2VyIl0sIngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6InVzZXIiLCJ4LWhhc3VyYS11c2VyLWlkIjoiMTUzODYzZjktZTQwMC00Njg2LTgyMTEtMzI0OGNjYWY2MGJhIiwieC1oYXN1cmEtdXNlci1pcy1hbm9ueW1vdXMiOiJmYWxzZSJ9LCJzdWIiOiIxNTM4NjNmOS1lNDAwLTQ2ODYtODIxMS0zMjQ4Y2NhZjYwYmEiLCJpc3MiOiJoYXN1cmEtYXV0aCIsImlhdCI6MTY1Mzg5MjA5NCwiZXhwIjoxNjUzODkyOTk0fQ.9nVL2Lj8KWBW3WrjJr4tPNH3_29qJKKKSDRNYebhiHI
|
||||
```
|
||||
|
||||
The decoded payload of this access token is a JSON object that looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"https://hasura.io/jwt/claims": {
|
||||
"x-hasura-allowed-roles": ["me", "user"],
|
||||
"x-hasura-default-role": "user",
|
||||
"x-hasura-user-id": "153863f9-e400-4686-8211-3248ccaf60ba",
|
||||
"x-hasura-user-is-anonymous": "false"
|
||||
},
|
||||
"sub": "153863f9-e400-4686-8211-3248ccaf60ba",
|
||||
"iss": "hasura-auth",
|
||||
"iat": 1653892094,
|
||||
"exp": 1653892994
|
||||
}
|
||||
```
|
||||
|
||||
The token contains information about the user id, default role, allowed roles, if the user is anonymous or not, and other metadata.
|
||||
|
||||
The claims under `https://hasura.io/jwt/claims` are the same claims that are used by the GraphQL API to create [permissions](/graphql/permissions). The claims (`x-hasura-*`) are also called permission variables. It's possible to add more [permission variables](/graphql/permissions#permission-variables) to the access token.
|
||||
|
||||
:::info
|
||||
You can manually decode an access token using [JWT.io](https://jwt.io/).
|
||||
:::
|
||||
|
||||
The token is cryptographically signed by Nhost Auth, which means that all other Nhost services can trust the information in the token.
|
||||
|
||||
:::info
|
||||
Use the `NHOST_JWT_SECRET` [system environment variable](/platform/environment-variables#system-environment-variables) to verify access tokens in [Serverless Functions](/serverless-functions). Here's a guide on how to [Get the authenticated user in a Serverless Function](https://github.com/nhost/nhost/discussions/278).
|
||||
:::
|
||||
|
||||
The access token can not be revoked. Instead, the token is only valid for 15 minutes. The user can get a new access token by using the refresh token.
|
||||
|
||||
## Refresh Token
|
||||
|
||||
A refresh token is used to request a new access token. Refresh tokens are long-lived tokens stored in the database in the `auth.refresh_tokens` table.
|
||||
|
||||
Refresh tokens are valid for 30 days.
|
||||
|
||||
To revoke a refresh token, simply delete it from the database.
|
||||
@@ -1,162 +0,0 @@
|
||||
---
|
||||
title: Users
|
||||
sidebar_label: Users
|
||||
sidebar_position: 1
|
||||
image: /img/og/users.png
|
||||
---
|
||||
|
||||
Users are stored in the `auth.users` table in the [database](/database).
|
||||
|
||||
## Creating Users
|
||||
|
||||
Users should be created using the sign-up or sign-in flows as described under [sign-in methods](/authentication/sign-in-methods).
|
||||
|
||||
- **Never** create users directly via GraphQL or database, unless you [import users](#import-users) from an external system.
|
||||
- **Never** modify the database schema for the `auth.users` table.
|
||||
- **Never** modify the GraphQL root queries or fields for any of the tables in the `auth` schema.
|
||||
|
||||
You're allowed to:
|
||||
|
||||
- Add and remove your GraphQL relationships for the `users` table and other tables in the `auth` schema.
|
||||
- Create, edit and delete permissions for the `users` table and other tables in the `auth` schema.
|
||||
|
||||
## Roles
|
||||
|
||||
Each user has one **default role** and an array of **allowed roles**. These roles are used to resolve permissions for requests to [GraphQL](/graphql/permissions) and [Storage](/storage#permissions).
|
||||
|
||||
When the user makes a request, only one role is used to resolve permissions. The default role is used if no role is explicitly specified. Users can only make requests using the default role or one of the allowed roles.
|
||||
|
||||
You can manage users' default roles and allowed roles in the Dashboard at **Auth**.
|
||||
|
||||
### Default Role
|
||||
|
||||
The default role is used when no role is specified in the request. By default, users' default role is `user`.
|
||||
|
||||
You can change what the default role for new users should be at **Settings -> Roles and Permissions**.
|
||||
|
||||
### Allowed Roles
|
||||
|
||||
Allowed roles are roles the user is allowed to use when making a request. Usually, you would change the role from `user` (the default role) to some other role because you want to use a different role to resolve permissions for a particular request.
|
||||
|
||||
By default, users have two allowed roles:
|
||||
|
||||
- `user` (also the default role)
|
||||
- `me`
|
||||
|
||||
You can change what the default role for new users should be at **Settings -> Roles and Permissions**.
|
||||
|
||||
#### Assign Allowed Roles
|
||||
|
||||
It's possible to give users a subset of allowed roles during signup.
|
||||
|
||||
**Example:** Only give the `user` role (without the `me` role) for the user's allowed roles:
|
||||
|
||||
```js
|
||||
await nhost.auth.signUp({
|
||||
email: 'joe@example.com',
|
||||
password: 'secret-password'
|
||||
options: {
|
||||
allowedRoles: ['user']
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Set Role for GraphQL Requests
|
||||
|
||||
When no request role is specified, the user's default role will be used:
|
||||
|
||||
```js
|
||||
await nhost.graphql.request(QUERY, {})
|
||||
```
|
||||
|
||||
If you want to make a GraphQL request using a specific role, you can do so by using the `x-hasura-role` header, like this:
|
||||
|
||||
```js
|
||||
await nhost.graphql.request(
|
||||
QUERY,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
'x-hasura-role': 'me'
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
If the request is not part of the user's allowed roles, the request will fail.
|
||||
|
||||
## Metadata
|
||||
|
||||
You can store custom information about the user in the `metadata` column of the `users` table. The `metadata` column is of type JSONB so any JSON data can be stored.
|
||||
|
||||
**Example:** Add metadata to a user during sign-up:
|
||||
|
||||
```js
|
||||
await nhost.auth.signUp({
|
||||
email: 'joe@example.com',
|
||||
password: 'secret-password',
|
||||
options: {
|
||||
metadata: {
|
||||
birthYear: 1989,
|
||||
town: 'Stockholm',
|
||||
likes: ['Postgres', 'GraphQL', 'Hasura', 'Authentication', 'Storage', 'Serverless Functions']
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Get User Information using GraphQL
|
||||
|
||||
**Example:** Get all users.
|
||||
|
||||
```graphql
|
||||
query {
|
||||
users {
|
||||
id
|
||||
displayName
|
||||
email
|
||||
metadata
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example:** Get a single user.
|
||||
|
||||
```graphql
|
||||
query {
|
||||
user(id: "<user-id>") {
|
||||
id
|
||||
displayName
|
||||
email
|
||||
metadata
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Import Users
|
||||
|
||||
If you have users in a different system, you can import them into Nhost. When importing users you should insert the users directly into the database instead of using the authentication endpoints (`/signup/email-password`) to avoid sending unnecessary transactional emails.
|
||||
|
||||
It's possible to insert users via GraphQL or SQL.
|
||||
|
||||
### GraphQL
|
||||
|
||||
Make a GraphQL request to insert a user like this:
|
||||
|
||||
```graphql
|
||||
mutation insertUser($user: users_insert_input!) {
|
||||
insertUser(object: $user) {
|
||||
id
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SQL
|
||||
|
||||
Connect directly to the database and insert a user like this:
|
||||
|
||||
```sql
|
||||
INSERT INTO auth.users (id, email, display_name, password_hash, ..) VALUES ('<user-id>', '<email>', '<display-name>', '<password-hash>', ..);
|
||||
```
|
||||
|
||||
Passwords are hashed using [bcrypt](https://en.wikipedia.org/wiki/Bcrypt).
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"label": "CLI",
|
||||
"position": 9
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
title: Nhost CLI
|
||||
sidebar_label: Overview
|
||||
sidebar_position: 1
|
||||
image: /img/og/cli.png
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
The Nhost CLI is used to run and develop your projects locally and deploy changes to the Nhost Platform.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the CLI with:
|
||||
|
||||
```bash
|
||||
sudo curl -L https://raw.githubusercontent.com/nhost/cli/main/get.sh | bash
|
||||
```
|
||||
|
||||
## Updates
|
||||
|
||||
To update the Nhost CLI to its latest version, run the following command:
|
||||
|
||||
```bash
|
||||
sudo nhost sw upgrade
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [Docker](https://www.docker.com/get-started)
|
||||
|
||||
## See also
|
||||
|
||||
- [Local Development](/cli/local-development)
|
||||
- [Migrate to Nhost Config](/cli/migrate-config)
|
||||
- [Multiple Projects in Parallel](/cli/multiple-projects)
|
||||
- [CLI Documentation](/cli)
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"label": "Database",
|
||||
"position": 4
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
---
|
||||
title: 'Event triggers'
|
||||
sidebar_position: 2
|
||||
image: /img/og/event-triggers.png
|
||||
---
|
||||
|
||||
Event Triggers enable you to invoke webhooks when a database event happens. Event Triggers are typically used to do post-processing tasks, using custom backend code, based on database events.
|
||||
|
||||
Event Triggers are associated with a specific table in the database and the following event types are available:
|
||||
|
||||
- **INSERT** - A row is inserted into a table.
|
||||
- **UPDATE** - A row is updated in a table.
|
||||
- **DELETE** - A row is deleted from a table.
|
||||
|
||||
:::info
|
||||
|
||||
It's currently only possible to create Event Triggers in the Hasura Console. We're working on adding support for creating Event Triggers in the Nhost Dashboard.
|
||||
|
||||
:::
|
||||
|
||||
### Example Use Case
|
||||
|
||||
Let's say you're building an e-commerce application and you want to send an email to the customer when a new order is placed. Orders are stored in the `orders` table in your database.
|
||||
|
||||
To send out an email every time a new order is placed, you create an event trigger that listens for the `INSERT` event on the `orders` table. Now every time an order is placed, the event trigger invokes a webhook with the order information, and the webhook sends out the email.
|
||||
|
||||
## Create Event Trigger
|
||||
|
||||
Event Triggers are managed in the Hasura Console. Select **Events** in the main menu and click **Create** to add an Event Trigger.
|
||||
|
||||
<video width="99%" autoPlay muted loop controls="true">
|
||||
<source src="/videos/hasura-create-event-trigger.mp4" type="video/mp4" />
|
||||
</video>
|
||||
|
||||
## Event Triggers and Serverless Functions
|
||||
|
||||
Event Triggers and [Serverless Functions](/serverless-functions) are a perfect combination to build powerful database-backend logic. Every Serverless Function is exposed as an HTTP endpoint and can be used as a webhook for Event Triggers.
|
||||
|
||||
### Format
|
||||
|
||||
When using Serverless Functions as webhooks you should configure the webhook using a combination of environment variables and endpoints like this:
|
||||
|
||||
```
|
||||
{{NHOST_FUNCTIONS_URL}}/orders-insert-send-email
|
||||
```
|
||||
|
||||

|
||||
|
||||
The `NHOST_FUNCTIONS_URL` is a [system environment variable](/platform/environment-variables#system-environment-variables) and available in production and in development environments using the [CLI](/cli).
|
||||
|
||||
### Security
|
||||
|
||||
To make sure incoming requests to your webhook comes from Hasura, and not some malicious third party, you can use a shared webhook secret between Hasura and your webhook handler (e.g. your Serverless Function).
|
||||
|
||||
It is recommended to use the `NHOST_WEBHOOK_SECRET`, which is a [system environment variable](/platform/environment-variables#system-environment-variables) and available in production and in development environments using the [CLI](/cli). The `NHOST_WEBHOOK_SECRET` is available both in Hasura and in every Serverless Function.
|
||||
|
||||
To set this up is a two-step process:
|
||||
|
||||
- Step 1: Add the header `nhost-webhook-secret` with the value `NHOST_WEBHOOK_SECRET` (From env var) when creating the Event Trigger in the Hasura Console.
|
||||
|
||||

|
||||
|
||||
- Step 2: Check the header `nhost-webhook-secret` for incoming requests and make sure the header is the same as the environment variable `NHOST_WEBHOOK_SECRET`.
|
||||
|
||||
Here is an example of how to check the header in a Serverless Function:
|
||||
|
||||
```js
|
||||
export default async function handler(req, res) {
|
||||
// Check header to make sure the request comes from Hasura
|
||||
if (req.headers['nhost-webhook-secret'] !== process.env.NHOST_WEBHOOK_SECRET) {
|
||||
return res.status(400).send('Incorrect webhook secret')
|
||||
}
|
||||
|
||||
// Do something
|
||||
// Example:
|
||||
// - Send an email
|
||||
// - Create a subscription in Stripe
|
||||
// - Generate a PDF
|
||||
// - Send a message to Slack or Discord
|
||||
// - Update some data in the database
|
||||
|
||||
console.log(JSON.stringify(req.body, null, 2))
|
||||
|
||||
return res.send('OK')
|
||||
}
|
||||
```
|
||||
|
||||
The `NHOST_WEBHOOK_SECRET` is a [system environment variable](/platform/environment-variables#system-environment-variables) and available in production and in development environments using the [CLI](/cli).
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Read the full [Event Triggers documentation from Hasura](https://hasura.io/docs/latest/graphql/core/event-triggers/index/).
|
||||
- Learn about the [GraphQL API](/graphql).
|
||||
@@ -1,169 +0,0 @@
|
||||
---
|
||||
title: 'Database'
|
||||
sidebar_position: 1
|
||||
sidebar_label: 'Overview'
|
||||
image: /img/og/database.png
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
Every Nhost project comes with its own [Postgres database](https://postgres.org/). Postgres is the world's most advanced open-source relational database and it's the most [popular SQL database for developers](https://insights.stackoverflow.com/survey/2021#section-most-loved-dreaded-and-wanted-databases).
|
||||
|
||||
There are three ways of managing your database:
|
||||
|
||||
1. Nhost Database UI (recommended).
|
||||
2. Hasura Console.
|
||||
3. [Connect directly to the database.](#postgres-access)
|
||||
|
||||
## Schemas
|
||||
|
||||
Generally, you should use the `public` schema for your project. It's also ok to add custom schemas for more advanced usage.
|
||||
|
||||
The two schemas `auth` and `storage` are reserved for [Nhost Auth](/authentication) and [Nhost Storage](/storage). You're allowed to modify **permissions** and **add relationships**. However, never modify any tables or remove relationships that were added by Nhost inside the `auth` and `storage` schemas.
|
||||
|
||||
## Manage Tables
|
||||
|
||||
### Create Table
|
||||
|
||||
1. Go to **Database** in the left menu.
|
||||
2. Click **New table**.
|
||||
3. Enter a **name** for the table.
|
||||
4. Add **columns**.
|
||||
5. Select a **Primary Key** (usually the `id` column).
|
||||
6. (Optional) Select an **Identity** column. Identity is for integer columns only and is usually selected for the `id` column so the `id` is automatically incremented (1,2,3...) on new rows.
|
||||
7. (Optional) Add **Foreign Keys**.
|
||||
8. Click **Create**.
|
||||
|
||||
When a table is created it is instantly available through the [GraphQL API](/graphql).
|
||||
|
||||
Here's an example of how to create a `customers` table:
|
||||
|
||||
<Tabs groupId="nhost-vs-sql">
|
||||
<TabItem value="nhost" label="Nhost" default>
|
||||
|
||||
<video width="99%" autoPlay muted loop controls="true">
|
||||
<source src="/videos/nhost-table-create.mp4" type="video/mp4" />
|
||||
</video>
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
CREATE TABLE "public"."customers" (
|
||||
"id" bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
"name" text NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Edit Table
|
||||
|
||||
1. Go to the **Database** in the left menu
|
||||
2. Click on the **context menu** of the table you want to change and click **Edit table**.
|
||||
3. **Edit** (add, change, delete) the table's columns.
|
||||
4. Click **Save**.
|
||||
|
||||
Here's an example of how to edit a `customers` table by adding an `address` column:
|
||||
|
||||
<Tabs groupId="nhost-vs-sql">
|
||||
<TabItem value="nhost" label="Nhost" default>
|
||||
|
||||
<video width="99%" autoPlay muted loop controls="true">
|
||||
<source src="/videos/nhost-table-edit.mp4" type="video/mp4" />
|
||||
</video>
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
ALTER TABLE "public"."customers" ADD COLUMN "address" text;
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Delete Table
|
||||
|
||||
1. Go to the **Database** in the left menu
|
||||
2. Click on the **context menu** of the table you want to delete and click **Delete table**.
|
||||
3. Click **Delete** to confirm deleting the table.
|
||||
|
||||
**Example:** Delete a `customers` table:
|
||||
|
||||
<Tabs groupId="nhost-vs-sql">
|
||||
<TabItem value="nhost" label="Nhost" default>
|
||||
|
||||
<video width="99%" autoPlay muted loop controls="true">
|
||||
<source src="/videos/nhost-table-delete.mp4" type="video/mp4" />
|
||||
</video>
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
DROP TABLE "public"."customers";
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Postgres Access
|
||||
|
||||
It's possible to access your Postgres database directly with your favorite Postgres client.
|
||||
|
||||
Go to **Settings** in the left menu and click on **Database**. You'll find the connection string and credentials to connect to your database.
|
||||
|
||||

|
||||
|
||||
### Reset Postgres Password
|
||||
|
||||
It's possible to reset the database password under **Settings** -> **Database** -> **Reset Password**.
|
||||
|
||||
<video width="99%" autoPlay muted loop controls="true">
|
||||
<source src="/videos/nhost-database-reset-password.mp4" type="video/mp4" />
|
||||
</video>
|
||||
|
||||
## Migrations
|
||||
|
||||
To track database changes, use the [Nhost CLI](/cli) to develop locally and use our [Git integration](/platform/git) to automatically deploy database migrations live.
|
||||
|
||||
1. Develop locally using the Nhost CLI.
|
||||
2. Push changes to GitHub.
|
||||
3. Nhost automatically deploys changes.
|
||||
|
||||
Learn how to do [development with the Nhost CLI](/cli/local-development).
|
||||
|
||||
## Seed Data
|
||||
|
||||
Seed data is a way of automatically adding data to your database using SQL when a new environment is created. This is, for the moment, only applicable when you're using the [Nhost CLI](/cli) to develop locally. When you're running `nhost up` for the first time, seed data is added.
|
||||
|
||||
In the future, seed data will also be added to new preview environments.
|
||||
|
||||
Seed data should be located in `nhost/seeds/default/` and are executed in alphabetical order.
|
||||
|
||||
**Example:** Two seed scripts with countries and products.
|
||||
|
||||
```text
|
||||
nhost/seeds/default/001-countries.sql
|
||||
nhost/seeds/default/002-products.sql
|
||||
```
|
||||
|
||||
## Backups
|
||||
|
||||
Databases on the [Pro and Enterprise plans](https://nhost.io/pricing) are automatically backed up daily.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Use lower-case names for tables. E.g. `customers` instead of `Customers`.
|
||||
- Use plural names for tables. E.g. `customers` instead of `customer`.
|
||||
- use underscore (`_`) instead of camelCase for table names. E.g. `customer_invoices` instead of `customerInvoices`.
|
||||
- use underscore (`_`) instead of camelCase for column names. E.g. `first_name` instead of `firstName`.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Learn PostgreSQL Tutorial - Full Course for Beginners (YouTube)](https://www.youtube.com/watch?v=qw--VYLpxG4).
|
||||
- Learn more about how to manage your [Postgres database in Hasura](https://hasura.io/docs/latest/graphql/core/databases/postgres/schema/index/).
|
||||
- Learn about the [GraphQL API](/graphql).
|
||||
@@ -1,84 +0,0 @@
|
||||
---
|
||||
title: 'Settings'
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
Below you can find the official schema (cue) and an example to configure your postgres database:
|
||||
|
||||
<Tabs groupId="package-manager">
|
||||
<TabItem value="schema" label="schema">
|
||||
|
||||
```cue
|
||||
#Postgres: {
|
||||
version: string | *"14.6-20230705-1"
|
||||
|
||||
// Resources for the service, optional
|
||||
resources?: #Resources & {
|
||||
replicas: 1
|
||||
}
|
||||
|
||||
// postgres settings of the same name in camelCase, optional
|
||||
settings?: {
|
||||
jit: "off" | "on" | *"on"
|
||||
maxConnections: int32 | *100
|
||||
sharedBuffers: string | *"128MB"
|
||||
effectiveCacheSize: string | *"4GB"
|
||||
maintenanceWorkMem: string | *"64MB"
|
||||
checkpointCompletionTarget: number | *0.9
|
||||
walBuffers: int32 | *-1
|
||||
defaultStatisticsTarget: int32 | *100
|
||||
randomPageCost: number | *4.0
|
||||
effectiveIOConcurrency: int32 | *1
|
||||
workMem: string | *"4MB"
|
||||
hugePages: string | *"try"
|
||||
minWalSize: string | *"80MB"
|
||||
maxWalSize: string | *"1GB"
|
||||
maxWorkerProcesses: int32 | *8
|
||||
maxParallelWorkersPerGather: int32 | *2
|
||||
maxParallelWorkers: int32 | *8
|
||||
maxParallelMaintenanceWorkers: int32 | *2
|
||||
}
|
||||
}
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="toml" label="toml" default>
|
||||
|
||||
```toml
|
||||
[postgres]
|
||||
version = '14.6-20230925-1'
|
||||
|
||||
[postgres.resources.compute]
|
||||
cpu = 1000
|
||||
memory = 2048
|
||||
|
||||
[postgres.settings]
|
||||
jit = "off"
|
||||
maxConnections = 100
|
||||
sharedBuffers = '256MB'
|
||||
effectiveCacheSize = '768MB'
|
||||
maintenanceWorkMem = '64MB'
|
||||
checkpointCompletionTarget = 0.9
|
||||
walBuffers = -1
|
||||
defaultStatisticsTarget = 100
|
||||
randomPageCost = 1.1
|
||||
effectiveIOConcurrency = 200
|
||||
workMem = '1310kB'
|
||||
hugePages = 'off'
|
||||
minWalSize = '80MB'
|
||||
maxWalSize = '1GB'
|
||||
maxWorkerProcesses = 8
|
||||
maxParallelWorkersPerGather = 2
|
||||
maxParallelWorkers = 8
|
||||
maxParallelMaintenanceWorkers = 2
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
:::info
|
||||
At the time of writing this document postgres settings are only supported via the [configuration file](https://nhost.io/blog/config).
|
||||
:::
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"label": "GraphQL API",
|
||||
"position": 5
|
||||
}
|
||||
@@ -1,350 +0,0 @@
|
||||
---
|
||||
title: 'GraphQL API'
|
||||
sidebar_position: 1
|
||||
sidebar_label: 'Overview'
|
||||
image: /img/og/graphql.png
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
A GraphQL API is automatically and instantly available based on the tables and columns in your [database](/database).
|
||||
|
||||
The GraphQL API has support for inserting, selecting, updating, and deleting data, which usually accounts for 80% of all API operations you need.
|
||||
|
||||
It's the [Hasura GraphQL engine](https://github.com/hasura/graphql-engine) that powers the GraphQL API which means that all documentation about [queries](https://hasura.io/docs/latest/graphql/core/databases/postgres/queries/index/), [mutations](https://hasura.io/docs/latest/graphql/core/databases/postgres/mutations/index/), and [subscriptions](https://hasura.io/docs/latest/graphql/core/databases/postgres/subscriptions/index/) from Hasura's documentation is applicable.
|
||||
|
||||
## What is GraphQL
|
||||
|
||||
GraphQL is a query language for APIs that prioritize developer experience. The GraphQL API can be used to both fetch (query) and modify (mutation) data. GraphQL is especially powerful for frontend developers who want to build products fast.
|
||||
|
||||
GraphQL has grown rapidly in popularity in the last years and has been adopted by almost all major tech companies such as Facebook, GitHub, and Stripe.
|
||||
|
||||
Building your GraphQL API is a lot of work, but with Nhost it's easy because every table and column is instantly available in your GraphQL API.
|
||||
|
||||
## Endpoint
|
||||
|
||||
The GraphQL API is available at `https://[subdomain].graphql.[region].nhost.run/v1` When using the [CLI](/cli) the GraphQL API is available at `https://local.graphql.nhost.run/v1`.
|
||||
|
||||
## GraphQL Clients for JavaScript
|
||||
|
||||
The [Nhost JavaScript client](/reference/javascript) comes with a simple [GraphQL client](/reference/javascript/graphql) that works well for the backend or simple applications.
|
||||
|
||||
When building more complex frontend applications, we recommend using a more advanced GraphQL client such as:
|
||||
|
||||
- [Apollo Client](https://www.apollographql.com/docs/react/):
|
||||
- [Nhost Apollo Client for React](/reference/react/apollo)
|
||||
- [Nhost Apollo Client for Vue](/reference/vue/apollo)
|
||||
- [URQL](https://formidable.com/open-source/urql/)
|
||||
- [React Query](https://react-query.tanstack.com/graphql)
|
||||
- [SWR](https://swr.vercel.app/docs/data-fetching#graphql)
|
||||
|
||||
## Queries
|
||||
|
||||
A query is used to fetch data from the GraphQL API.
|
||||
|
||||
:::tip
|
||||
The [Queries documentation from Hasura](https://hasura.io/docs/latest/graphql/core/databases/postgres/queries/index/) is applicable since we're using Hasura's GraphQL Engine for your project.
|
||||
:::
|
||||
|
||||
**Example:** A GraphQL query to select `title`, `body`, and `isCompleted` for every row in the `todos` table.
|
||||
|
||||
<Tabs >
|
||||
<TabItem value="request" label="Request" default>
|
||||
|
||||
```graphql
|
||||
query GetTodos {
|
||||
todos {
|
||||
title
|
||||
body
|
||||
isCompleted
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="response" label="Response">
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"todos": [
|
||||
{
|
||||
"title": "Delete Firebase account",
|
||||
"body": "Migrate to Nhost",
|
||||
"isCompleted": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Filtering and sorting
|
||||
|
||||
More complex queries utilize filters, limits, sorting, and aggregation.
|
||||
|
||||
Here's an example of a more complex GraphQL query that selects all items in the `todos` table that are not completed, with the total number of comments and the last five comments:
|
||||
|
||||
<Tabs >
|
||||
<TabItem value="request" label="Request" default>
|
||||
|
||||
```graphql
|
||||
query GetTodosWithLatestComments {
|
||||
todos(where: { isCompleted: { _eq: false } }) {
|
||||
title
|
||||
body
|
||||
comments(limit: 5, order_by: { createdAt: desc }) {
|
||||
comment
|
||||
createdAt
|
||||
author
|
||||
}
|
||||
comments_aggregate {
|
||||
aggregate {
|
||||
count(columns: id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="response" label="Response">
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"todos": [
|
||||
{
|
||||
"title": "Delete Firebase account",
|
||||
"body": "Migrate to Nhost",
|
||||
"comments": [
|
||||
{
|
||||
"comment": "Let's do this",
|
||||
"created_at": "2019-10-31T08:34:25.621167+00:00",
|
||||
"author": "John"
|
||||
},
|
||||
{
|
||||
"comment": "🎉",
|
||||
"created_at": "2019-10-31T08:33:18.465623+00:00",
|
||||
"author": "Charlie"
|
||||
}
|
||||
],
|
||||
"comments_aggregate": {
|
||||
"aggregate": {
|
||||
"count": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Mutations
|
||||
|
||||
A GraphQL mutation is used to insert, upsert, update, or delete data.
|
||||
|
||||
:::tip
|
||||
The [Mutations documentation from Hasura](https://hasura.io/docs/latest/graphql/core/databases/postgres/mutations/index/) is applicable since we're using Hasura's GraphQL Engine for your project.
|
||||
:::
|
||||
|
||||
### Insert Data
|
||||
|
||||
**Example:** A GraphQL mutation to insert data:
|
||||
|
||||
<Tabs >
|
||||
<TabItem value="request" label="Request" default>
|
||||
|
||||
```graphql
|
||||
mutation InsertTodo {
|
||||
insert_todos(
|
||||
objects: [{ title: "Delete Firebase account", body: "Migrate to Nhost", isCompleted: false }]
|
||||
) {
|
||||
returning {
|
||||
id
|
||||
title
|
||||
body
|
||||
isCompleted
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="response" label="Response">
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"insert_todos": [
|
||||
{
|
||||
"id": "bf4b01ec-8eb6-451b-afac-81f5058ce852",
|
||||
"title": "Delete Firebase account",
|
||||
"body": "Migrate to Nhost",
|
||||
"isCompleted": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
#### Insert Multiple Rows
|
||||
|
||||
Use an array of objects to insert multiple rows at the same time.
|
||||
|
||||
**Example:** Insert multiple Todos at the same time:
|
||||
|
||||
```graphql title=GraphQL
|
||||
mutation InsertMultipleTodos {
|
||||
insert_todos(
|
||||
objects: [
|
||||
{ title: "Build the front end", body: "Mobile app or website first?", isCompleted: false }
|
||||
{ title: "Launch 🚀", body: "That was easy", isCompleted: false }
|
||||
]
|
||||
) {
|
||||
returning {
|
||||
id
|
||||
title
|
||||
body
|
||||
isCompleted
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Update Data
|
||||
|
||||
You can update existing data with an update mutation. You can update multiple rows at once.
|
||||
|
||||
**Example:** A GraphQL mutation to mark a atodo item as completed:
|
||||
|
||||
```graphql title=GraphQL
|
||||
mutation UpdateTodoStatus($id: uuid, $isCompleted: Boolean) {
|
||||
update_todos(_set: { isCompleted: $isCompleted }, where: { id: { _eq: $id } }) {
|
||||
returning {
|
||||
body
|
||||
isCompleted
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notice how we are using variables as the `id` and `isDone` variables, which lets us mark any todo as completed or not completed with the same mutation.
|
||||
|
||||
### Upsert Data
|
||||
|
||||
When you're not sure if a piece of data already exists, use an upsert mutation. It will either insert an object into the database if it doesn't exist or update the fields of an existing object.
|
||||
|
||||
Unlike for update mutations, you must pass all columns to an upsert mutation.
|
||||
|
||||
To convert your insert mutation to an upsert, you need to add an `on_conflict` property for the GraphQL API to know which fields it should use to find duplicates.
|
||||
|
||||
The `on_conflict` key must be a unique key in your database:
|
||||
|
||||
```graphql title=GraphQL
|
||||
mutation UpsertTodo {
|
||||
insert_todos(
|
||||
objects: { title: "Delete Firebase account", body: "...", isCompleted: false }
|
||||
on_conflict: { constraint: todos_title_key, update_columns: [title, isCompleted] }
|
||||
) {
|
||||
returning {
|
||||
id
|
||||
title
|
||||
body
|
||||
isCompleted
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This will update `body` and `done` of the todos with the title "Delete Firebase account".
|
||||
|
||||
#### Conditional upsert
|
||||
|
||||
Inserts a new object into a table, or if the primary key already exists, updates columns if the `where` condition is met.
|
||||
|
||||
For example, you may want to only update an existing todo if it is not done:
|
||||
|
||||
```graphql
|
||||
mutation UpsertTodo {
|
||||
insert_todos(
|
||||
objects: { title: "Delete Firebase account", body: "...", done: false }
|
||||
on_conflict: {
|
||||
constraint: todos_title_key
|
||||
update_columns: [body, done]
|
||||
where: { done: { _eq: false } }
|
||||
}
|
||||
) {
|
||||
returning {
|
||||
body
|
||||
done
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Ignore mutation on conflict
|
||||
|
||||
If `update_columns` is empty, the mutation will be ignored if the object already exists.
|
||||
|
||||
Here we have set the `title` to a unique key, to prevent multiple tasks with the same name. We want to avoid overwriting existing todos, so the update_columns array is empty:
|
||||
|
||||
```graphql
|
||||
mutation InsertTodo {
|
||||
insert_todos(
|
||||
objects: { title: "Delete Firebase account", body: "...", done: false }
|
||||
on_conflict: { constraint: todos_title_key, update_columns: [] }
|
||||
) {
|
||||
returning {
|
||||
body
|
||||
done
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this case, the insert mutation is ignored because a todo with the `title` `"Delete Firebase account"` already exists, and `update_columns` is empty.
|
||||
|
||||
### Delete Data
|
||||
|
||||
To delete your data, use a delete mutation. This mutation will delete all `todos` where `done` is `true`:
|
||||
|
||||
```graphql title="GraphQL mutation"
|
||||
mutation DeleteDoneTodos {
|
||||
delete_todos(where: { done: { _eq: true } }) {
|
||||
affected_rows
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you have set up foreign keys which will restrict a delete violation, you will get an error and will not be able to delete the data until all violations are solved. The simplest way to solve this is by set `On Delete Violation` to `CASCADE` when you set up a foreign key.
|
||||
|
||||
## Subscriptions
|
||||
|
||||
GraphQL subscriptions are queries that use WebSockets to keep the data up to date in your app in real-time. You only have to change `query` to `subscription` when constructing the GraphQL document:
|
||||
|
||||
```graphql title="GraphQL subscription"
|
||||
subscription GetTodos {
|
||||
todos {
|
||||
title
|
||||
body
|
||||
done
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Your data is always in sync when using subscriptions. It does not matter if the data changes through GraphQL or directly in the database. The data is always syncing in real-time using GraphQL subscriptions.
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"label": "Remote Schemas",
|
||||
"position": 11
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
---
|
||||
title: Stripe GraphQL API
|
||||
sidebar_label: Stripe
|
||||
sidebar_position: 2
|
||||
image: /img/og/graphql.png
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
This package creates a Stripe GraphQL API, allowing for interaction with data at Stripe.
|
||||
|
||||
Here's an example of how to use the Stripe GraphQL API to get a list of invoices for a specific Stripe customer:
|
||||
|
||||
<Tabs >
|
||||
<TabItem value="request" label="Request" default>
|
||||
|
||||
```graphql
|
||||
query {
|
||||
stripe {
|
||||
customer(id: "cus_xxx") {
|
||||
id
|
||||
name
|
||||
invoices {
|
||||
data {
|
||||
id
|
||||
created
|
||||
paid
|
||||
hostedInvoiceUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="response" label="Response">
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"stripe": {
|
||||
"customer": {
|
||||
"id": "cus_xxx",
|
||||
"name": "joe@example.com",
|
||||
"invoices": {
|
||||
"data": [
|
||||
{
|
||||
"id": "in_1MUmwnCCF9wuB4xxxxxxxx",
|
||||
"created": 1674806769,
|
||||
"paid": true,
|
||||
"hostedInvoiceUrl": "https://invoice.stripe.com/i/acct_xxxxxxx/test_YWNjdF8xS25xV1lDQ0Y5d3VCNGZYLF9ORkhWxxxxxxxxxxxx?s=ap"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
It's recommended to add the Stripe GraphQL API as a [Remote Schema in Hasura](https://hasura.io/docs/latest/remote-schemas/index/) and connect data from your database with data in Stripe. By doing so, it's possible to request data from your database and Stripe in a single GraphQL query.
|
||||
|
||||
Here's an example of how to use the Stripe GraphQL API to get a list of invoices for a specific Stripe customer. Note that the user data is fetched from your database and the Stripe customer data is fetched from Stripe:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
users {
|
||||
# User in your database
|
||||
id
|
||||
displayName
|
||||
userData {
|
||||
stripeCustomerId # Customer's Stripe Customer Id
|
||||
stripeCustomer {
|
||||
# Data from Stripe
|
||||
id
|
||||
name
|
||||
paymentMethods {
|
||||
id
|
||||
card {
|
||||
brand
|
||||
last4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Get Started
|
||||
|
||||
Install the package:
|
||||
|
||||
<Tabs groupId="package-manager">
|
||||
<TabItem value="npm" label="npm" default>
|
||||
|
||||
```bash
|
||||
npm install @nhost/stripe-graphql-js
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn install @nhost/stripe-graphql-js
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @nhost/stripe-graphql-js
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Serverless Function
|
||||
|
||||
Create a new [Serverless Function](/serverless-functions): `functions/graphql/stripe.ts`:
|
||||
|
||||
```ts
|
||||
import { createStripeGraphQLServer } from '@nhost/stripe-graphql-js'
|
||||
|
||||
const server = createStripeGraphQLServer()
|
||||
|
||||
export default server
|
||||
```
|
||||
|
||||
> You can run the Stripe GraphQL API in any Node.js environment because it's built using [GraphQL Yoga](https://github.com/dotansimha/graphql-yoga).
|
||||
|
||||
## Stripe Secret Key
|
||||
|
||||
Add `STRIPE_SECRET_KEY` as an environment variable.
|
||||
|
||||
If you're using Nhost, add `STRIPE_SECRET_KEY` to `nhost.toml` like this:
|
||||
|
||||
```
|
||||
[[ global.environment ]]
|
||||
name=STRIPE_SECRET_KEY
|
||||
value='{{ secrets.STRIPE_SECRET_KEY }}'
|
||||
```
|
||||
|
||||
And then add to your `.secrets` file:
|
||||
|
||||
```
|
||||
STRIPE_SECRET_KEY=sk_test_***
|
||||
```
|
||||
|
||||
In production set your secret with your stripe production key (`sk_live_***`) in the Nhost dashboard.
|
||||
|
||||
Learn more about [Stripe API keys](https://stripe.com/docs/keys#obtain-api-keys).
|
||||
|
||||
## Start Nhost
|
||||
|
||||
```
|
||||
nhost up
|
||||
```
|
||||
|
||||
Learn more about the [Nhost CLI](/cli).
|
||||
|
||||
## Test
|
||||
|
||||
Test the Stripe GraphQL API in the browser:
|
||||
|
||||
[https://local.functions.nhost.run/v1/graphql/stripe](https://local.functions.nhost.run/v1/graphql/stripe)
|
||||
|
||||
## Remote Schema
|
||||
|
||||
Add the Stripe GraphQL API as a Remote Schema in Hasura.
|
||||
|
||||
**URL**
|
||||
|
||||
```
|
||||
{{NHOST_FUNCTIONS_URL}}/graphql/stripe
|
||||
```
|
||||
|
||||
**Headers**
|
||||
|
||||
```
|
||||
x-nhost-webhook-secret: NHOST_WEBHOOK_SECRET (From env var)
|
||||
```
|
||||
|
||||
> The `NHOST_WEBHOOK_SECRET` is used to verify that the request is coming from Nhost. The environment variable is a [system environment variable](/platform/environment-variables#system-environment-variables) and is always available.
|
||||
|
||||

|
||||
|
||||
## Permissions
|
||||
|
||||
Here's a minimal example without any custom permissions. Only requests using the `x-hasura-admin-secret` header will work:
|
||||
|
||||
```js
|
||||
const server = createStripeGraphQLServer()
|
||||
```
|
||||
|
||||
For more granular permissions, you can pass an `isAllowed` function to the `createStripeGraphQLServer`. The `isAllowed` function takes a `stripeCustomerId` and [`context`](#context) as parameters and runs every time the GraphQL server makes a request to Stripe to get or modify data for a specific Stripe customer.
|
||||
|
||||
Here is an example of an `isAllowed` function:
|
||||
|
||||
```ts
|
||||
import { createStripeGraphQLServer } from '@nhost/stripe-graphql-js'
|
||||
|
||||
const isAllowed = (stripeCustomerId: string, context: Context) => {
|
||||
const { isAdmin, userClaims } = context
|
||||
|
||||
// allow all requests if they have a valid `x-hasura-admin-secret`
|
||||
if (isAdmin) {
|
||||
return true
|
||||
}
|
||||
|
||||
// get user id
|
||||
const userId = userClaims['x-hasura-user-id']
|
||||
|
||||
// check if the user is signed in
|
||||
if (!userId) {
|
||||
return false
|
||||
}
|
||||
|
||||
// get more user information from the database
|
||||
const { user } = await gqlSDK.getUser({
|
||||
id: userId
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if the user is part of a workspace with the `stripeCustomerId`
|
||||
return user.workspaceMembers.some((workspaceMember) => {
|
||||
return workspaceMember.workspace.stripeCustomerId === stripeCustomerId
|
||||
})
|
||||
}
|
||||
|
||||
const server = createStripeGraphQLServer({ isAllowed })
|
||||
|
||||
export default server
|
||||
```
|
||||
|
||||
### Context
|
||||
|
||||
The `context` object contains:
|
||||
|
||||
- `userClaims` - verified JWT claims from the user's access token.
|
||||
- `isAdmin` - `true` if the request was made using a valid `x-hasura-admin-secret` header.
|
||||
- `request` - [Fetch API Request object](https://developer.mozilla.org/en-US/docs/Web/API/Request) that represents the incoming HTTP request in platform-independent way. It can be useful for accessing headers to authenticate a user
|
||||
- `query` - the DocumentNode that was parsed from the GraphQL query string
|
||||
- `operationName` - the operation name selected from the incoming query
|
||||
- `variables` - the variables that were defined in the query
|
||||
- `extensions` - the extensions that were received from the client
|
||||
|
||||
Read more about the [default context from GraphQL Yoga](https://www.the-guild.dev/graphql/yoga-server/docs/features/context#default-context).
|
||||
|
||||
## Source Code
|
||||
|
||||
The source code is available on [GitHub](https://github.com/nhost/nhost/tree/main/integrations/stripe-graphql-js).
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
title: 'Introduction to Nhost'
|
||||
sidebar_label: Introduction
|
||||
image: /img/og/introduction-to-nhost.png
|
||||
---
|
||||
|
||||
Nhost is the open source GraphQL backend (Firebase Alternative) and a development platform. Nhost is doing for the backend, what [Netlify](https://netlify.com/) and [Vercel](https://vercel.com/) are doing for the frontend.
|
||||
|
||||
We provide a modern backend with the general building blocks required to build fantastic digital products.
|
||||
|
||||
We make it easy to build and deploy this backend using our platform which takes care of configuration, security, and performance. Things just work and scale automatically so you can focus on your product and your business.
|
||||
|
||||
## Quickstart
|
||||
|
||||
Get started quickly by following one of our quickstart guides:
|
||||
|
||||
- [Next.js](/quickstarts/nextjs)
|
||||
- [React](/quickstarts/react)
|
||||
- [RedwoodJS](/quickstarts/redwoodjs)
|
||||
- [Vue](/quickstarts/vue)
|
||||
|
||||
## Products and Features
|
||||
|
||||
Learn more about the product and features of Nhost.
|
||||
|
||||
- [Database](/database)
|
||||
- [GraphQL API](/graphql)
|
||||
- [Authentication](/authentication)
|
||||
- [Storage](/storage)
|
||||
- [Serverless Functions](/serverless-functions)
|
||||
- [Run](/run)
|
||||
|
||||
## Architecture
|
||||
|
||||
Nhost is a Backend-as-a-Service built with open source tools to provide developers the general building blocks required to build fantastic digital apps and products.
|
||||
|
||||
Here's a diagram of the Nhost stack on a high level:
|
||||
|
||||

|
||||
|
||||
As you see in the image above, Nhost provides endpoints for:
|
||||
|
||||
- GraphQL API (`/graphql`)
|
||||
- Authentication (`/auth`)
|
||||
- Storage (`/storage`)
|
||||
- Functions (`/functions`)
|
||||
|
||||
Data is stored in Postgres and files are stored in S3.
|
||||
|
||||
## Open Source
|
||||
|
||||
The open source tools used for the full Nhost stack are:
|
||||
|
||||
- Database: [Postgres](https://www.postgresql.org/)
|
||||
- GraphQL API: [Hasura](https://github.com/hasura/graphql-engine)
|
||||
- Authentication: [Hasura Auth](https://github.com/nhost/hasura-auth)
|
||||
- Storage: [Hasura Storage](https://github.com/nhost/hasura-storage)
|
||||
- Functions: [Node.js](https://nodejs.org/en/)
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
title: 'Introduction to Nhost'
|
||||
sidebar_label: Introduction
|
||||
image: /img/og/introduction-to-nhost.png
|
||||
---
|
||||
|
||||
Nhost is the open source GraphQL backend (Firebase Alternative) and a development platform. Nhost is doing for the backend, what [Netlify](https://netlify.com/) and [Vercel](https://vercel.com/) are doing for the frontend.
|
||||
|
||||
We provide a modern backend with the general building blocks required to build fantastic digital products.
|
||||
|
||||
We make it easy to build and deploy this backend using our platform which takes care of configuration, security, and performance. Things just work and scale automatically so you can focus on your product and your business.
|
||||
|
||||
## Quickstart
|
||||
|
||||
Get started quickly by following one of our quickstart guides:
|
||||
|
||||
- [Next.js](/quickstarts/nextjs)
|
||||
- [React](/quickstarts/react)
|
||||
- [RedwoodJS](/quickstarts/redwoodjs)
|
||||
- [Vue](/quickstarts/vue)
|
||||
|
||||
## Products and Features
|
||||
|
||||
Learn more about the product and features of Nhost.
|
||||
|
||||
- [Database](/database)
|
||||
- [GraphQL API](/graphql)
|
||||
- [Authentication](/authentication)
|
||||
- [Storage](/storage)
|
||||
- [Serverless Functions](/serverless-functions)
|
||||
|
||||
## Architecture
|
||||
|
||||
Nhost is a Backend-as-a-Service built with open source tools to provide developers the general building blocks required to build fantastic digital apps and products.
|
||||
|
||||
Here's a diagram of the Nhost stack on a high level:
|
||||
|
||||

|
||||
|
||||
As you see in the image above, Nhost provides endpoints for:
|
||||
|
||||
- GraphQL API (`/graphql`)
|
||||
- Authentication (`/auth`)
|
||||
- Storage (`/storage`)
|
||||
- Functions (`/functions`)
|
||||
|
||||
Data is stored in Postgres and files are stored in S3.
|
||||
|
||||
## Open Source
|
||||
|
||||
The open source tools used for the full Nhost stack are:
|
||||
|
||||
- Database: [Postgres](https://www.postgresql.org/)
|
||||
- GraphQL API: [Hasura](https://github.com/hasura/graphql-engine)
|
||||
- Authentication: [Hasura Auth](https://github.com/nhost/hasura-auth)
|
||||
- Storage: [Hasura Storage](https://github.com/nhost/hasura-storage)
|
||||
- Functions: [Node.js](https://nodejs.org/en/)
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"label": "Platform",
|
||||
"position": 10
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
title: 'Compute Resources'
|
||||
sidebar_position: 1
|
||||
image: /img/og/platform/compute-resources.png
|
||||
---
|
||||
|
||||
Compute resources are the fundamental units that represent the processing power and memory available to your Nhost projects. The primary compute resources are vCPU and RAM. This documentation outlines the key aspects of compute resources in the context of the Nhost Cloud Platform.
|
||||
|
||||
|
||||
### Shared vs Dedicated Compute
|
||||
|
||||
Free Projects are given a total of 2 shared vCPUs and 1 GiB of RAM:
|
||||
|
||||
- Postgres: 0.5 vCPU / 256 MiB
|
||||
- Hasura GraphQL: 0.5 vCPU / 384 MiB
|
||||
- Auth: 0.5 vCPU / 256 MiB
|
||||
- Storage: 0.5 vCPU / 128 MiB
|
||||
|
||||
Pro Projects are given a total of 2 shared vCPUs and 2 GiB of RAM:
|
||||
|
||||
- Postgres: 0.5 vCPU / 512 MiB
|
||||
- Hasura GraphQL: 0.5 vCPU / 768 MiB
|
||||
- Auth: 0.5 vCPU / 384 MiB
|
||||
- Storage: 0.5 vCPU / 384 MiB
|
||||
|
||||
This is fine if your apps mostly run at low to medium load, occasionally burst for brief periods of time, and can tolerate drops in performance. It is important to understand that the availability of CPU time is not guaranteed.
|
||||
|
||||
### Dedicated Compute
|
||||
|
||||
On the other hand, for high production workloads where latency is important, or variable performance is not at all tolerable, you should consider configuring your project to use dedicated compute resources.
|
||||
With dedicated compute, resources are guaranteed for your project so you don't have to contend for them.
|
||||
|
||||
In addition to the resources fully dedicated to the project, apps are allowed to burst if demand requires it and resources are available. If properly sized, dedicated resources should guarantee the performance of your application while allowing for occassional burts.
|
||||
|
||||
To configure dedicated compute to your projects, all you have to do is navigate to the project's settings, and click on "Compute Resources" (see image below). There you will be able to choose the total amount of resources you want to dedicate, and spread those resources amongst all services.
|
||||
|
||||
|
||||

|
||||
|
||||
To further improve availability and fault tolerance, you can also leverage Service Replicas. To learn more, check out the documentation for [Service Replicas](https://docs.nhost.io/platform/service-replicas).
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
title: 'Environment Variables'
|
||||
sidebar_position: 2
|
||||
image: /img/og/platform/environment-variables.png
|
||||
---
|
||||
|
||||
Environment Variables are key-value pairs configured outside your source code. They are used to store environment-specific values such as API keys.
|
||||
|
||||
Environment Variables are available for:
|
||||
|
||||
- [Hasura GraphQL Engine](/graphql)
|
||||
- [Serverless Functions](/serverless-functions)
|
||||
|
||||
When an Environment Variable has updated the changes happen immediately for Hasura GraphQL Engine. For Serverless Functions, a new deployment via [Git](/platform/git) is required.
|
||||
|
||||
## Custom Environment Variables
|
||||
|
||||
You can manage your project's Environment Variables in the Nhost Dashboard or by using the configuration file.
|
||||
|
||||
### Dashboard
|
||||
|
||||

|
||||
|
||||
Environment Variables can be managed in the Nhost Dashboard under **Settings** → **Environment Variables**.
|
||||
|
||||
### Configuration File
|
||||
|
||||
Environment Variables can also be managed by adding new `[[global.environment]]` sections to the `nhost.toml` file.
|
||||
|
||||
```toml
|
||||
[global]
|
||||
[[global.environment]]
|
||||
name = 'MY_ENV_VAR'
|
||||
value = '<first-value>'
|
||||
|
||||
[[global.environment]]
|
||||
name = 'MY_OTHER_ENV_VAR'
|
||||
value = '<second-value>'
|
||||
|
||||
# ... omitted for brevity
|
||||
```
|
||||
|
||||
These environment variables will also be available on the Nhost Dashboard after committing and pushing the changes to your Git repository.
|
||||
|
||||
## System Environment Variables
|
||||
|
||||
System environment variables are automatically generated from the configuration file and your project's subdomain and region.
|
||||
|
||||
The following system environment variables are available:
|
||||
|
||||
- `NHOST_ADMIN_SECRET`
|
||||
- `NHOST_WEBHOOK_SECRET`
|
||||
- ~~`NHOST_BACKEND_URL`~~ ([deprecated](https://github.com/nhost/nhost/discussions/1319))
|
||||
- `NHOST_SUBDOMAIN`
|
||||
- `NHOST_REGION`
|
||||
- `NHOST_HASURA_URL`
|
||||
- `NHOST_AUTH_URL`
|
||||
- `NHOST_GRAPHQL_URL`
|
||||
- `NHOST_STORAGE_URL`
|
||||
- `NHOST_FUNCTIONS_URL`
|
||||
- `NHOST_JWT_SECRET`
|
||||
|
||||
`NHOST_ADMIN_SECRET`, `NHOST_WEBHOOK_SECRET` and `NHOST_JWT_SECRET` are populated with values from the configuration file. The rest of the system environment variables are populated with values from your project's subdomain and region.
|
||||
|
||||
**Example values**:
|
||||
|
||||
```text
|
||||
NHOST_ADMIN_SECRET=e7w36ag287qn5qry795f6ymm57qgvqup
|
||||
|
||||
NHOST_WEBHOOK_SECRET=ns3sfjgdw4y6zeqthwnnw347dzh8wyj4
|
||||
|
||||
NHOST_BACKEND_URL=https://abc123abc.nhost.run
|
||||
|
||||
NHOST_SUBDOMAIN=abv123abc
|
||||
|
||||
NHOST_REGION=eu-central-1
|
||||
|
||||
NHOST_HASURA_URL=https://abc123abc.hasura.eu-central-1.nhost.run/console
|
||||
|
||||
NHOST_AUTH_URL=https://abc123abc.auth.eu-central-1.nhost.run/v1
|
||||
|
||||
NHOST_GRAPHQL_URL=https://abc123abc.graphql.eu-central-1.nhost.run/v1
|
||||
|
||||
NHOST_STORAGE_URL=https://abc123abc.storage.eu-central-1.nhost.run/v1
|
||||
|
||||
NHOST_FUNCTIONS_URL=https://abc123abc.functions.eu-central-1.nhost.run/v1
|
||||
|
||||
NHOST_JWT_SECRET={"type": "HS256", "key": "vumpbe2w2mgaqj5yqfp7dvxu6kywtvsgb68ejpdaqxerea8jwrsszdp2dhkjxsh4df69pzm3ja6ukedx8ja43zdt6q9kgbgg2w9vh2sedeppukud9a2qzy29v3afdn7m"}
|
||||
```
|
||||
@@ -1,54 +0,0 @@
|
||||
---
|
||||
title: 'Git'
|
||||
sidebar_position: 2
|
||||
image: /img/og/platform/github-integration.png
|
||||
---
|
||||
|
||||
Nhost allows you to automatically deploy your Nhost project when you do changes to your Git repository.
|
||||
|
||||
## Supported Git Providers
|
||||
|
||||
- GitHub
|
||||
|
||||
Support GitLab, BitBucket, and other Git providers are on our roadmap.
|
||||
|
||||
## Deployment
|
||||
|
||||
The following things are deployed:
|
||||
|
||||
- Database migrations
|
||||
- Hasura metadata
|
||||
- Serverless Functions
|
||||
- Email Templates
|
||||
|
||||
:::caution
|
||||
Settings in `nhost/config.yaml` are **not** deployed. That means you need to manually sync settings between local and remote environments between the CLI and Nhost Cloud.
|
||||
:::
|
||||
|
||||
## Using GitHub
|
||||
|
||||
1. From your Nhost project, click **Connect to Github**.
|
||||
|
||||

|
||||
|
||||
2. **Install the Nhost project** on your Github account.
|
||||
|
||||

|
||||
|
||||
3. **Connect** your Github repository.
|
||||
|
||||

|
||||
|
||||
## Deployment Branch
|
||||
|
||||
Nhost only deploys changes from the **Deployment Branch**. By default, your deployment branch matches the default branch (usually `main`).
|
||||
|
||||
You can change the Deployment Branch on the **Git** page in the **Settings** section.
|
||||
|
||||
It's possible to have multiple Nhost projects connected to the same Git repository and use different Deployment Branches (e.g., `main` and `staging`). Learn more about [multiple environments](/platform/multiple-environments).
|
||||
|
||||
## Base Directory
|
||||
|
||||
If your Nhost project is not located at the root of your Git repository, which is typically the case when using a monorepo, it's possible to set a custom Base Directory. The Base Directory is where the `nhost/` directory is located. In other words, the Base Directory is the **parent directory** of the `nhost/` directory.
|
||||
|
||||
You can change the Base Directory on the **Git** page in the **Settings** section.
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
title: 'Metrics (beta)'
|
||||
sidebar_position: 1
|
||||
image: /img/og/platform/metrics.png
|
||||
---
|
||||
|
||||
Metrics provide insights into your Nhost projects by integrating a managed Grafana instance tailored to them. This feature is available on the Pro plan and allows you to analyze your project's performance, identify potential bottlenecks, and optimize your application.
|
||||
|
||||
### Available Dashboards
|
||||
|
||||
Nhost Metrics includes a few pre-configured dashboards with the following metrics:
|
||||
|
||||
- vCPU/memory usage by Service replica for all services
|
||||
- Throttling time / percentage
|
||||
- Postgres volume usage
|
||||
- Networking
|
||||
- [Functions](/serverless-functions) metrics like calls, response times, errors, etc.
|
||||
|
||||

|
||||
|
||||
### Accessing Metrics
|
||||
|
||||
Metrics can be found in your project's dashboard.
|
||||
|
||||

|
||||
|
||||
### Limitations (Beta)
|
||||
|
||||
Please note that while Metrics is in beta, its functionality and pricing might change.
|
||||
|
||||
- Users cannot save or use custom dashboards in the current beta version.
|
||||
- Additional categories of metrics like application and database metrics will be added in future updates.
|
||||
- Future updates will centralize logs using Loki and integrate alerts directly into the Grafana dashboard.
|
||||
|
||||
Using Nhost Metrics allows you to identify bottlenecks in your applications, which you can then fix by leveraging [Compute Resources](https://docs.nhost.io/platform/compute) and [Service Replicas](https://docs.nhost.io/platform/service-replicas) to address these performance issues.
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
title: 'Multiple Environments'
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
Here's a guide on how to use Nhost with multiple environments and how to set up a workflow around them.
|
||||
|
||||
For this example, we'll set up one **production environment** and one **staging environment**.
|
||||
|
||||
## Git
|
||||
|
||||
Create a new Git repo and use the [CLI](/cli) and the `nhost init` command to initialize a new project.
|
||||
|
||||
Also, create a new branch called `staging`.
|
||||
|
||||
You should now have a Git repo with a `main` branch and a `staging` branch.
|
||||
|
||||
## Nhost Cloud
|
||||
|
||||
To have two environments, we need to create two projects on Nhost Cloud.
|
||||
|
||||
Both projects must be connected to the **same** [Git](/platform/git) repo, but using **different** [deployment branches](/platform/git#deployment-branch).
|
||||
|
||||
Set your production project to use the `main` branch as the Deployment Branch and your staging project to use the `staging` branch as the Deployment Branch. This way, the production project will only deploy new changes to the `main` branch and the staging project will only deploy new changes to the `staging` branch.
|
||||
|
||||
## Development
|
||||
|
||||
### Local
|
||||
|
||||
Now, use the CLI to do [local development](/cli/local-development). And use a specific feature branch while doing development.
|
||||
|
||||
### Staging
|
||||
|
||||
Once you're ready to test your changes to staging, create a pull request from your feature branch to the `staging` branch. Then, merge the pull request to `staging`.
|
||||
|
||||
This will automatically trigger a new deployment to the staging project on the Nhost platform.
|
||||
|
||||
### Production
|
||||
|
||||
Once you've tested your changes in the staging environment, you can create a new pull request from the `staging` branch to the `main` branch. Then, merge the pull request to `main`.
|
||||
|
||||
This will automatically trigger a new deployment to the production project on the Nhost platform.
|
||||
|
||||
## Configuration Overlays
|
||||
|
||||
While Nhost uses a single file to deploy all of the environments connected to the same repository and branch, overlays allow you to accommodate for minor differences in those environments by allowing you to define rules to modify the base configuration. For details on overlays head to [Configuration Overlays](/cli/overlays)
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
Now you have two environments, one for staging and one for production. You can use this workflow to do local development, and test your changes in a staging environment before deploying them to production.
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
title: 'Service Replicas'
|
||||
sidebar_position: 1
|
||||
image: /img/og/platform/service-replicas.png
|
||||
---
|
||||
|
||||
Service Replicas is a feature that allows you to create multiple replicas of your services, enhancing availability and fault tolerance for your Nhost apps. By distributing user requests among replicas, your apps can handle more traffic and provide a better user experience. This documentation touches on the key aspects of Service Replicas in the context of the Nhost Cloud Platform.
|
||||
|
||||

|
||||
|
||||
To read the announcement, check out our [blog post](https://nhost.io/blog/service-replicas).
|
||||
|
||||
### Supported Services
|
||||
|
||||
Replicas can be configured for the following services:
|
||||
|
||||
- Hasura
|
||||
- Auth
|
||||
- Storage
|
||||
|
||||
Currently, we don't support replicas for Postgres.
|
||||
|
||||
### Configuring Service Replicas
|
||||
|
||||
To configure Service Replicas for your project, follow these steps:
|
||||
|
||||
1. Navigate to your project's settings in the Nhost Dashboard.
|
||||
2. Click on "Compute Resources".
|
||||
3. Locate the "Replicas" slider for each service (Hasura, Auth, and Storage).
|
||||
4. Adjust the number of replicas for each service as needed.
|
||||
5. Save your changes.
|
||||
|
||||
Please note that when setting multiple replicas for a service, the 1:2 ratio between CPU and RAM for that service has to be respected. With only one replica, this ratio does not need to be respected at the service level.
|
||||
|
||||
### Benefits
|
||||
|
||||
- Improved fault tolerance: Multiple replicas ensure that if one instance crashes duo to an unexpected issue, the other replicas can continue to serve user requests.
|
||||
- Improved availability: Distributing user requests among multiple replicas allows your apps to handle more traffic and maintain a high level of performance.
|
||||
- Load balancing: Distributing workloads evenly among replicas to prevent bottlenecks and ensure smooth performance during peak times.
|
||||
|
||||
### Pricing
|
||||
|
||||
Pricing is based on the size of each replica and the total number of replicas you have configured for each service:
|
||||
|
||||
- If you allocate 1 vCPU and 2 GiB of RAM to the auth service, for example, the cost for a single replica is $50.
|
||||
- If you configure 3 replicas for auth, the total cost for all replicas would be $150 (3 replicas x $50 per replica).
|
||||
|
||||
Please note that Service Replicas is available only for projects on the Pro plan or higher.
|
||||
|
||||
### Caveats
|
||||
|
||||
- Postgres replication: As mentioned earlier, we do not have support for multiple replicas of Postgres. This feature may be added in the future.
|
||||
- Resource ratio: When configuring multiple replicas for a service, you must adhere to the 1:2 ratio between CPU and RAM for that service.
|
||||
|
||||

|
||||
|
||||
For more information on compute resources and scaling your apps, refer to our documentation on [Compute Resources](https://docs.nhost.io/platform/compute).
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"label": "Quickstarts",
|
||||
"position": 3,
|
||||
"collapsed": false
|
||||
}
|
||||
@@ -1,707 +0,0 @@
|
||||
---
|
||||
title: 'Quickstart: Next.js'
|
||||
sidebar_label: Next.js
|
||||
sidebar_position: 2
|
||||
image: /img/og/quickstart-nextjs.png
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
# Quickstart: Next.js
|
||||
|
||||
## Introduction
|
||||
|
||||
This quickstart provides the steps you need to build a Next.js app
|
||||
powered by Nhost for the backend. It includes:
|
||||
|
||||
- Database: [PostgreSQL](https://www.postgresql.org/)
|
||||
- Instant GraphQL API: [Hasura](https://hasura.io/)
|
||||
- Authentication: [Hasura Auth](https://github.com/nhost/hasura-auth/)
|
||||
- Storage: [Hasura Storage](https://hub.docker.com/r/nhost/hasura-storage)
|
||||
|
||||
By the end of this guide, you'll have a full-stack app that allows users to sign
|
||||
in to access a protected dashboard and update their profile information.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before getting started, let's make sure that your development environment is
|
||||
ready.
|
||||
|
||||
You'll need **Node.js** version 12 or later: [install it from here](https://nodejs.org/en/).
|
||||
|
||||
## Project setup
|
||||
|
||||
### Create a new Nhost app
|
||||
|
||||
import CreateApp from '@site/src/components/create-nhost-project.mdx'
|
||||
|
||||
<CreateApp />
|
||||
|
||||
:::info
|
||||
You can also connect your Nhost project to a Git repository at GitHub. When you do this, any updates you push to your code will automatically be deployed. [Learn more](/platform/git)
|
||||
:::
|
||||
|
||||
## Initialize the app
|
||||
|
||||
### Create a Next.js app
|
||||
|
||||
The simplest way to create a new Next.js application is by using the tool called
|
||||
`create-next-app`, which bootstraps a Next.js app for you without the hassle of
|
||||
configuring everything yourself.
|
||||
|
||||
So, open your terminal, and run the following command:
|
||||
|
||||
```bash
|
||||
npx create-next-app my-nhost-app --example "https://github.com/nhost/quickstart-nextjs"
|
||||
```
|
||||
|
||||
:::info
|
||||
This command uses an [existing template](https://github.com/nhost/quickstart-nextjs), through the `--example` flag, which already contains the React components and pages we'll use for this guide.
|
||||
:::
|
||||
|
||||
You can now `cd` into your project directory:
|
||||
|
||||
```bash
|
||||
cd my-nhost-app
|
||||
```
|
||||
|
||||
And run the development server with the following command:
|
||||
|
||||
<Tabs groupId="package-manager">
|
||||
<TabItem value="npm" label="npm" default>
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
If everything is working fine, your Next.js development server should be running
|
||||
on port 3000. Open [http://localhost:3000](http://localhost:3000) from your
|
||||
browser to check this out.
|
||||
|
||||
### Configure Nhost with Next.js
|
||||
|
||||
To work with Nhost from within our Next.js app, we'll use the
|
||||
[Next.js SDK](https://github.com/nhost/nhost/tree/main/packages/nextjs) provided
|
||||
by Nhost. It's a wrapper around the
|
||||
[Nhost React SDK](https://github.com/nhost/nhost/tree/main/packages/react) which
|
||||
gives us a way to interact with our Nhost backend using React hooks.
|
||||
|
||||
You can install the Nhost Next.js SDK with:
|
||||
|
||||
<Tabs groupId="package-manager">
|
||||
<TabItem value="npm" label="npm" default>
|
||||
|
||||
```bash
|
||||
npm install @nhost/nextjs graphql
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @nhost/nextjs graphql
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Next, open your `_app.js` file as we'll now configure Nhost inside our app.
|
||||
|
||||
The Nhost Next.js SDK comes with a React provider named `NhostProvider` that
|
||||
makes the authentication state and all the provided React hooks available in our
|
||||
application.
|
||||
|
||||
Use the following code to instantiate a new Nhost client and link it to your
|
||||
Nhost backend:
|
||||
|
||||
```jsx title="pages/_app.js"
|
||||
import { UserProvider } from '../UserProvider';
|
||||
// highlight-start
|
||||
import { NhostProvider, NhostClient } from '@nhost/nextjs';
|
||||
// highlight-end
|
||||
|
||||
// highlight-start
|
||||
const nhost = new NhostClient({
|
||||
subdomain: process.env.NEXT_PUBLIC_NHOST_SUBDOMAIN || '',
|
||||
region: process.env.NEXT_PUBLIC_NHOST_REGION || ''
|
||||
});
|
||||
// highlight-end
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return (
|
||||
{/* highlight-next-line */}
|
||||
<NhostProvider nhost={nhost} initial={pageProps.nhostSession}>
|
||||
<UserProvider>
|
||||
{/* ... */}
|
||||
</UserProvider>
|
||||
{/* highlight-next-line */}
|
||||
</NhostProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Finally, store the environment variables for `subdomain` and `region` in `.env.development`:
|
||||
|
||||
```yaml title=".env.development"
|
||||
NEXT_PUBLIC_NHOST_SUBDOMAIN=[subdomain]
|
||||
NEXT_PUBLIC_NHOST_REGION=[region]
|
||||
```
|
||||
|
||||
You find your Nhost project's `subdomain` and `region` in the [project overview](https://app.nhost.io):
|
||||
|
||||

|
||||
|
||||
:::caution
|
||||
Don't forget to restart your Next.js server after saving your `.env.development`
|
||||
file to load your new environment variable.
|
||||
:::
|
||||
|
||||
:::info Nhost CLI
|
||||
Do you use the Nhost CLI? Learn how to set `subdomain` and `region` in the [CLI documentation](/cli#subdomain-and-region).
|
||||
:::
|
||||
|
||||
## Build the app
|
||||
|
||||
### Add authentication
|
||||
|
||||
#### 1. Sign-up
|
||||
|
||||
The next step is to allow our users to authenticate into our application.
|
||||
Let's start with implementing the sign-up process.
|
||||
|
||||
For that, we'll use the `useSignUpEmailPassword` hook provided by the Nhost
|
||||
Next.js SDK within our `SignUp` component.
|
||||
|
||||
So, open up the corresponding file from your project, and use the following
|
||||
code:
|
||||
|
||||
```jsx title="components/SignUp.js"
|
||||
import styles from '../styles/components/SignUp.module.css'
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useSignUpEmailPassword } from '@nhost/nextjs'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import Input from './Input'
|
||||
import Spinner from './Spinner'
|
||||
|
||||
const SignUp = () => {
|
||||
const [firstName, setFirstName] = useState('')
|
||||
const [lastName, setLastName] = useState('')
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const { signUpEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } =
|
||||
useSignUpEmailPassword()
|
||||
|
||||
const handleOnSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
await signUpEmailPassword(email, password, {
|
||||
displayName: `${firstName} ${lastName}`.trim(),
|
||||
metadata: {
|
||||
firstName,
|
||||
lastName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (isSuccess) {
|
||||
router.push('/')
|
||||
return null
|
||||
}
|
||||
|
||||
const disableForm = isLoading || needsEmailVerification
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.card}>
|
||||
<div className={styles['logo-wrapper']}>
|
||||
<Image src="/logo.svg" alt="logo" layout="fill" objectFit="contain" />
|
||||
</div>
|
||||
|
||||
{needsEmailVerification ? (
|
||||
<p className={styles['verification-text']}>
|
||||
Please check your mailbox and follow the verification link to verify your email.
|
||||
</p>
|
||||
) : (
|
||||
<form onSubmit={handleOnSubmit} className={styles.form}>
|
||||
<div className={styles['input-group']}>
|
||||
<Input
|
||||
label="First name"
|
||||
value={firstName}
|
||||
onChange={(e) => setFirstName(e.target.value)}
|
||||
disabled={disableForm}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Last name"
|
||||
value={lastName}
|
||||
onChange={(e) => setLastName(e.target.value)}
|
||||
disabled={disableForm}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
type="email"
|
||||
label="Email address"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
disabled={disableForm}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
label="Create password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
disabled={disableForm}
|
||||
required
|
||||
/>
|
||||
|
||||
<button type="submit" disabled={disableForm} className={styles.button}>
|
||||
{isLoading ? <Spinner size="sm" /> : 'Create account'}
|
||||
</button>
|
||||
|
||||
{isError ? <p className={styles['error-text']}>{error?.message}</p> : null}
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className={styles.text}>
|
||||
Already have an account?{' '}
|
||||
<Link href="/sign-in">
|
||||
<a className={styles.link}>Sign in</a>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SignUp
|
||||
```
|
||||
|
||||
By default, the user must verify his email address before fully signing up. You can change this setting from your Nhost dashboard.
|
||||
|
||||
#### 2. Sign-in
|
||||
|
||||
Now that new users can sign up for our application, let's see how to allow
|
||||
existing users to sign in with email and password.
|
||||
|
||||
For that, we will use the Nhost hook named `useSignInEmailPassword` inside our
|
||||
`SignIn` component the same way we did with our `SignUp` component. So, here's
|
||||
what your component should look like after applying the changes for the sign-in
|
||||
logic:
|
||||
|
||||
```jsx title="components/SignIn.js"
|
||||
import styles from '../styles/components/SignIn.module.css'
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useSignInEmailPassword } from '@nhost/nextjs'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import Input from './Input'
|
||||
import Spinner from './Spinner'
|
||||
|
||||
const SignIn = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const { signInEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } =
|
||||
useSignInEmailPassword()
|
||||
|
||||
const handleOnSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
await signInEmailPassword(email, password)
|
||||
}
|
||||
|
||||
if (isSuccess) {
|
||||
router.push('/')
|
||||
return null
|
||||
}
|
||||
|
||||
const disableForm = isLoading || needsEmailVerification
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.card}>
|
||||
<div className={styles['logo-wrapper']}>
|
||||
<Image src="/logo.svg" alt="logo" layout="fill" objectFit="contain" />
|
||||
</div>
|
||||
|
||||
{needsEmailVerification ? (
|
||||
<p className={styles['verification-text']}>
|
||||
Please check your mailbox and follow the verification link to verify your email.
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<form onSubmit={handleOnSubmit} className={styles.form}>
|
||||
<Input
|
||||
type="email"
|
||||
label="Email address"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
disabled={disableForm}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
label="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
disabled={disableForm}
|
||||
required
|
||||
/>
|
||||
|
||||
<button type="submit" disabled={disableForm} className={styles.button}>
|
||||
{isLoading ? <Spinner size="sm" /> : 'Sign in'}
|
||||
</button>
|
||||
|
||||
{isError ? <p className={styles['error-text']}>{error?.message}</p> : null}
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className={styles.text}>
|
||||
No account yet?{' '}
|
||||
<Link href="/sign-up">
|
||||
<a className={styles.link}>Sign up</a>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SignIn
|
||||
```
|
||||
|
||||
#### 3. Sign-out
|
||||
|
||||
Finally, to allow the users to sign out from the app, we can use the Nhost
|
||||
`useSignOut` hook:
|
||||
|
||||
```jsx title="components/Layout.js"
|
||||
import { useSignOut } from '@nhost/nextjs'
|
||||
|
||||
const Layout = ({ children = null }) => {
|
||||
const { signOut } = useSignOut()
|
||||
|
||||
const menuItems = [
|
||||
//..
|
||||
{
|
||||
label: 'Logout',
|
||||
onClick: signOut,
|
||||
icon: LogoutIcon
|
||||
}
|
||||
]
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
### Protect routes
|
||||
|
||||
Now that we have implemented authentication, we can easily decide who can access
|
||||
certain parts of our application.
|
||||
|
||||
In our case, we'll only allow authenticated users to have access to the `/` and
|
||||
`/profile` routes. All the other users should be redirected to the `/sign-in`
|
||||
page if they try to access those routes.
|
||||
|
||||
To do so, we can check the authentication status of the current user using the
|
||||
Nhost SDK by creating a
|
||||
[high-order component](https://reactjs.org/docs/higher-order-components.html):
|
||||
|
||||
```jsx title="withAuth.js"
|
||||
import styles from './styles/pages/ProtectedRoute.module.css'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useAuthenticationStatus } from '@nhost/nextjs'
|
||||
import Spinner from './components/Spinner'
|
||||
|
||||
export default function withAuth(Component) {
|
||||
return function AuthProtected(props) {
|
||||
const router = useRouter()
|
||||
const { isLoading, isAuthenticated } = useAuthenticationStatus()
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Spinner />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
router.push('/sign-in')
|
||||
return null
|
||||
}
|
||||
|
||||
return <Component {...props} />
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, wrap our Next.js pages, `index.js` and `profile.js`, with it:
|
||||
|
||||
<Tabs
|
||||
defaultValue="index"
|
||||
values={[
|
||||
{label: 'pages/index.js', value: 'index'},
|
||||
{label: 'pages/profile.js', value: 'profile'},
|
||||
]}>
|
||||
<TabItem value="index">
|
||||
|
||||
```js
|
||||
import withAuth from '../withAuth'
|
||||
|
||||
const Home = () => {
|
||||
//...
|
||||
}
|
||||
|
||||
export default withAuth(Home)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="profile">
|
||||
|
||||
```js
|
||||
import withAuth from '../withAuth'
|
||||
|
||||
const Profile = () => {
|
||||
//...
|
||||
}
|
||||
|
||||
export default withAuth(Profile)
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Retrieve user data
|
||||
|
||||
Finally, let's display the information of the authenticated user throughout his
|
||||
dashboard to make the app more personalized.
|
||||
|
||||
Getting the current authenticated user data is quite easy. We
|
||||
can use the `useUserData` hook provided by Nhost to do it.
|
||||
|
||||
So, open the `UserProvider.js` file and use this hook like so:
|
||||
|
||||
```js
|
||||
import React, { useContext } from 'react'
|
||||
// highlight-next-line
|
||||
import { useUserData } from '@nhost/nextjs'
|
||||
|
||||
const UserContext = React.createContext(null)
|
||||
|
||||
export function UserProvider({ children = null }) {
|
||||
// highlight-next-line
|
||||
const user = useUserData()
|
||||
return <UserContext.Provider value={{ user }}>{children}</UserContext.Provider>
|
||||
}
|
||||
|
||||
export function useUserContext() {
|
||||
return useContext(UserContext)
|
||||
}
|
||||
```
|
||||
|
||||
That's it! The JSX code for rendering the user data (email, display name, etc.)
|
||||
is already included in your components as part of the example repository you've
|
||||
bootstrapped at the beginning of this guide.
|
||||
|
||||
### Update user data
|
||||
|
||||
Nhost provides a GraphQL API through Hasura so that we can query and mutate our
|
||||
data instantly.
|
||||
|
||||
In this tutorial, we'll use the
|
||||
[Apollo GraphQL client](https://www.apollographql.com/) for interacting with
|
||||
this GraphQL API.
|
||||
|
||||
So, start by installing the following dependencies:
|
||||
|
||||
<Tabs groupId="package-manager">
|
||||
<TabItem value="npm" label="npm" default>
|
||||
|
||||
```bash
|
||||
npm install @nhost/react-apollo @apollo/client
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @nhost/react-apollo @apollo/client
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Then, add the `NhostApolloProvider` from `@nhost/react-apollo` into your
|
||||
`_app_.js` file.
|
||||
|
||||
```jsx title="pages/_app.js"
|
||||
import { NhostApolloProvider } from '@nhost/react-apollo'
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return (
|
||||
<NhostProvider nhost={nhost} initial={pageProps.nhostSession}>
|
||||
<NhostApolloProvider nhost={nhost}>{/* ... */}</NhostApolloProvider>
|
||||
</NhostProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
From there, we can construct our GraphQL query and use the Apollo `useMutation`
|
||||
hook to execute that query when the user submits the form from the profile page:
|
||||
|
||||
```js title="pages/profile.js"
|
||||
import { gql, useMutation } from '@apollo/client'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
const UPDATE_USER_MUTATION = gql`
|
||||
mutation ($id: uuid!, $displayName: String!, $metadata: jsonb) {
|
||||
updateUser(pk_columns: { id: $id }, _set: { displayName: $displayName, metadata: $metadata }) {
|
||||
id
|
||||
displayName
|
||||
metadata
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const Profile = () => {
|
||||
const [mutateUser, { loading: updatingProfile }] = useMutation(UPDATE_USER_MUTATION)
|
||||
|
||||
const updateUserProfile = async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
try {
|
||||
await mutateUser({
|
||||
variables: {
|
||||
id: user.id,
|
||||
displayName: `${firstName} ${lastName}`.trim(),
|
||||
metadata: {
|
||||
firstName,
|
||||
lastName
|
||||
}
|
||||
}
|
||||
})
|
||||
toast.success('Updated successfully', { id: 'updateProfile' })
|
||||
} catch (error) {
|
||||
toast.error('Unable to update profile', { id: 'updateProfile' })
|
||||
}
|
||||
}
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Finally, since Hasura has an **allow nothing by default** policy, and we haven't
|
||||
set any permissions yet, our GraphQL mutations would fail.
|
||||
|
||||
So, open the Hasura console from the **Data** tab of your project from [your Nhost dashboard](https://app.nhost.io/). Then, go to the **permissions** tab of the `users` table, type in `user` in the role
|
||||
cell, and click the edit icon on the `select` operation:
|
||||
|
||||

|
||||
|
||||
To restrict the user to read his data only, specify a condition with the
|
||||
user's ID and the `X-Hasura-User-ID` session variable, which is passed with each
|
||||
requests.
|
||||
|
||||

|
||||
|
||||
Next, select the columns you'd like the users to have access to, and click
|
||||
**Save Permissions**.
|
||||
|
||||

|
||||
|
||||
Repeat the same steps on the `update` operation for the `user` role to allow
|
||||
users to update their `displayName` and `metadata` only.
|
||||
|
||||
Finally, to add caching, synchronizing, and updating server state in your Next.js app, let's refactor the user data fetching by using the Apollo client and our GraphQL API instead.
|
||||
|
||||
So, first add the following GraphQL query to retrieve the current user data from the `UserProvider.js` file:
|
||||
|
||||
```js title="UserProvider.js"
|
||||
import { gql } from '@apollo/client'
|
||||
|
||||
const GET_USER_QUERY = gql`
|
||||
query GetUser($id: uuid!) {
|
||||
user(id: $id) {
|
||||
id
|
||||
email
|
||||
displayName
|
||||
metadata
|
||||
avatarUrl
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export function UserProvider() {
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Then, replace the `useUserData` hook with the `useUserId` hook to retrieve the current user's ID only.
|
||||
|
||||
```js title="UserProvider.js"
|
||||
import { useUserId } from '@nhost/nextjs'
|
||||
|
||||
export function UserProvider() {
|
||||
const id = useUserId()
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Finally, we can run our GraphQL query using the `useQuery` hook and the current user's ID.
|
||||
|
||||
```jsx title="UserProvider.js"
|
||||
// highlight-next-line
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
|
||||
export function UserProvider({ children = null }) {
|
||||
const id = useUserId()
|
||||
// highlight-start
|
||||
const { loading, error, data } = useQuery(GET_USER_QUERY, {
|
||||
variables: { id },
|
||||
skip: !id
|
||||
})
|
||||
const user = data?.user
|
||||
// highlight-end
|
||||
|
||||
// highlight-start
|
||||
if (error) {
|
||||
return <p>Something went wrong. Try to refresh the page.</p>
|
||||
}
|
||||
if (loading) {
|
||||
return null
|
||||
}
|
||||
// highlight-end
|
||||
|
||||
return <UserContext.Provider value={{ user }}>{children}</UserContext.Provider>
|
||||
}
|
||||
```
|
||||
|
||||
You now have a fully functional Next.js application. Congratulations!
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Did you enjoy Nhost? Give us a star ⭐ on [Github](https://github.com/nhost/nhost). Thank you!
|
||||
- Check out our more in-depth [examples](https://github.com/nhost/nhost/tree/main/examples).
|
||||
- Build your next app with [Nhost](https://app.nhost.io/)!
|
||||
@@ -1,682 +0,0 @@
|
||||
---
|
||||
title: 'Quickstart: React'
|
||||
sidebar_label: React
|
||||
sidebar_position: 1
|
||||
image: /img/og/quickstart-react.png
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
# Quickstart: React
|
||||
|
||||
## Introduction
|
||||
|
||||
This quickstart guide provides the steps you need to build a simple React app
|
||||
powered by Nhost for the backend. It includes:
|
||||
|
||||
- Database: [PostgreSQL](https://www.postgresql.org/)
|
||||
- Instant GraphQL API: [Hasura](https://hasura.io/)
|
||||
- Authentication: [Hasura Auth](https://github.com/nhost/hasura-auth/)
|
||||
- Storage: [Hasura Storage](https://hub.docker.com/r/nhost/hasura-storage)
|
||||
|
||||
By the end of this guide, you'll have a full-stack app that allows users to log
|
||||
in to access a protected dashboard and update their profile information.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before getting started, let's make sure that your development environment is
|
||||
ready.
|
||||
|
||||
You'll need **Node.js** version 14 or later: [install it from here](https://nodejs.org/en/).
|
||||
|
||||
## Project setup
|
||||
|
||||
### Create a new Nhost project
|
||||
|
||||
import CreateProject from '@site/src/components/create-nhost-project.mdx'
|
||||
|
||||
<CreateProject />
|
||||
|
||||
:::info
|
||||
You can also connect your Nhost project to a Git repository at GitHub. When you do this, any updates you push to your code will automatically be deployed. [Learn more](/platform/git)
|
||||
:::
|
||||
|
||||
## Initialize the app
|
||||
|
||||
### Create a React app
|
||||
|
||||
The simplest way to create a new React application is by using the tool called
|
||||
`create-react-app`, which bootstraps a React app for you without the hassle of
|
||||
configuring everything yourself.
|
||||
|
||||
So, open your terminal, and run the following command:
|
||||
|
||||
```bash
|
||||
npx create-react-app my-nhost-app --template nhost-quickstart
|
||||
```
|
||||
|
||||
:::info
|
||||
This command uses an [existing template](https://github.com/nhost/cra-template-nhost-quickstart), through the `--template` flag, which already contains the React components and pages we'll use for this guide.
|
||||
:::
|
||||
|
||||
You can now `cd` into your project directory:
|
||||
|
||||
```bash
|
||||
cd my-nhost-app
|
||||
```
|
||||
|
||||
And run the development server with the following command:
|
||||
|
||||
<Tabs groupId="package-manager">
|
||||
<TabItem value="npm" label="npm" default>
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
If everything is working fine, your React development server should be running
|
||||
on port 3000. Open [http://localhost:3000](http://localhost:3000) from your
|
||||
browser to check this out.
|
||||
|
||||
### Configure Nhost with React
|
||||
|
||||
To work with Nhost from within our React app, we'll use the
|
||||
[React SDK](https://github.com/nhost/nhost/tree/main/packages/react) provided
|
||||
by Nhost. It's a wrapper around the
|
||||
[Nhost JavaScript SDK](https://github.com/nhost/nhost/tree/main/packages/nhost-js) which
|
||||
gives us a way to interact with our Nhost backend using React hooks.
|
||||
|
||||
You can install the Nhost React SDK with:
|
||||
|
||||
<Tabs groupId="package-manager">
|
||||
<TabItem value="npm" label="npm" default>
|
||||
|
||||
```bash
|
||||
npm install @nhost/react graphql
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @nhost/react graphql
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Next, open your `App.js` file as we'll now configure Nhost inside our app.
|
||||
|
||||
The Nhost React SDK comes with a React provider named `NhostProvider` that
|
||||
makes the authentication state and all the provided React hooks available in our
|
||||
application.
|
||||
|
||||
Use the following code to instantiate a new Nhost client and link it to your
|
||||
Nhost backend:
|
||||
|
||||
```jsx title="src/App.js"
|
||||
import { NhostClient, NhostProvider } from '@nhost/react'
|
||||
|
||||
const nhost = new NhostClient({
|
||||
subdomain: process.env.REACT_APP_NHOST_SUBDOMAIN,
|
||||
region: process.env.REACT_APP_NHOST_REGION
|
||||
})
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<NhostProvider nhost={nhost}>
|
||||
<BrowserRouter>{/* ... */}</BrowserRouter>
|
||||
</NhostProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
```
|
||||
|
||||
Finally, make sure to create an environment variable named
|
||||
`REACT_APP_NHOST_SUBDOMAIN` and `REACT_APP_NHOST_REGION` to store your Nhost domain details:
|
||||
|
||||
```yaml title=".env.local"
|
||||
REACT_APP_NHOST_SUBDOMAIN=[subdomain]
|
||||
REACT_APP_NHOST_REGION=[region]
|
||||
```
|
||||
|
||||
You find your Nhost project's `subdomain` and `region` in the [project overview](https://app.nhost.io):
|
||||
|
||||

|
||||
|
||||
:::caution
|
||||
Don't forget to restart your React server after saving your `.env.local`
|
||||
file to load your new environment variable.
|
||||
:::
|
||||
|
||||
:::info Nhost CLI
|
||||
Do you use the Nhost CLI? Learn how to set `subdomain` and `region` in the [CLI documentation](/cli#subdomain-and-region).
|
||||
:::
|
||||
|
||||
## Build the app
|
||||
|
||||
### Add authentication
|
||||
|
||||
#### 1. Sign-up
|
||||
|
||||
The next step is to allow our users to authenticate into our application.
|
||||
Let's start with implementing the sign-up process.
|
||||
|
||||
For that, we'll use the `useSignUpEmailPassword` hook provided by the Nhost
|
||||
React SDK within our `SignUp` component.
|
||||
|
||||
So, open up the corresponding file from your project, and use the following
|
||||
code:
|
||||
|
||||
```jsx title="src/components/SignUp.js"
|
||||
import styles from '../styles/components/SignUp.module.css'
|
||||
import { useState } from 'react'
|
||||
import { useSignUpEmailPassword } from '@nhost/react'
|
||||
import { Link, Navigate } from 'react-router-dom'
|
||||
import Input from './Input'
|
||||
import Spinner from './Spinner'
|
||||
|
||||
const SignUp = () => {
|
||||
const [firstName, setFirstName] = useState('')
|
||||
const [lastName, setLastName] = useState('')
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
|
||||
const { signUpEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } =
|
||||
useSignUpEmailPassword()
|
||||
|
||||
const handleOnSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
signUpEmailPassword(email, password, {
|
||||
displayName: `${firstName} ${lastName}`.trim(),
|
||||
metadata: {
|
||||
firstName,
|
||||
lastName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (isSuccess) {
|
||||
return <Navigate to="/" replace={true} />
|
||||
}
|
||||
|
||||
const disableForm = isLoading || needsEmailVerification
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.card}>
|
||||
<div className={styles['logo-wrapper']}>
|
||||
<img src={process.env.PUBLIC_URL + 'logo.svg'} alt="logo" />
|
||||
</div>
|
||||
|
||||
{needsEmailVerification ? (
|
||||
<p className={styles['verification-text']}>
|
||||
Please check your mailbox and follow the verification link to verify your email.
|
||||
</p>
|
||||
) : (
|
||||
<form onSubmit={handleOnSubmit} className={styles.form}>
|
||||
<div className={styles['input-group']}>
|
||||
<Input
|
||||
label="First name"
|
||||
value={firstName}
|
||||
onChange={(e) => setFirstName(e.target.value)}
|
||||
disabled={disableForm}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Last name"
|
||||
value={lastName}
|
||||
onChange={(e) => setLastName(e.target.value)}
|
||||
disabled={disableForm}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
type="email"
|
||||
label="Email address"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
disabled={disableForm}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
label="Create password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
disabled={disableForm}
|
||||
required
|
||||
/>
|
||||
|
||||
<button type="submit" disabled={disableForm} className={styles.button}>
|
||||
{isLoading ? <Spinner size="sm" /> : 'Create account'}
|
||||
</button>
|
||||
|
||||
{isError ? <p className={styles['error-text']}>{error?.message}</p> : null}
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className={styles.text}>
|
||||
Already have an account?{' '}
|
||||
<Link to="/sign-in" className={styles.link}>
|
||||
Sign in
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SignUp
|
||||
```
|
||||
|
||||
By default, the user must verify his email address before fully signing up. You can change this setting from your Nhost dashboard.
|
||||
|
||||
#### 2. Sign-in
|
||||
|
||||
Now that new users can sign up for our application, let's see how to allow
|
||||
existing users to sign in with email and password.
|
||||
|
||||
For that, we will use the Nhost hook named `useSignInEmailPassword` inside our
|
||||
`SignIn` component the same way we did with our `SignUp` component. So, here's
|
||||
what your component should look like after applying the changes for the sign-in
|
||||
logic:
|
||||
|
||||
```jsx title="src/components/SignIn.js"
|
||||
import styles from '../styles/components/SignIn.module.css'
|
||||
import { useState } from 'react'
|
||||
import { useSignInEmailPassword } from '@nhost/react'
|
||||
import { Link, Navigate } from 'react-router-dom'
|
||||
import Input from './Input'
|
||||
import Spinner from './Spinner'
|
||||
|
||||
const SignIn = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
|
||||
const { signInEmailPassword, isLoading, isSuccess, needsEmailVerification, isError, error } =
|
||||
useSignInEmailPassword()
|
||||
|
||||
const handleOnSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
signInEmailPassword(email, password)
|
||||
}
|
||||
|
||||
if (isSuccess) {
|
||||
return <Navigate to="/" replace={true} />
|
||||
}
|
||||
|
||||
const disableForm = isLoading || needsEmailVerification
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.card}>
|
||||
<div className={styles['logo-wrapper']}>
|
||||
<img src={process.env.PUBLIC_URL + 'logo.svg'} alt="logo" />
|
||||
</div>
|
||||
|
||||
{needsEmailVerification ? (
|
||||
<p className={styles['verification-text']}>
|
||||
Please check your mailbox and follow the verification link to verify your email.
|
||||
</p>
|
||||
) : (
|
||||
<form onSubmit={handleOnSubmit} className={styles.form}>
|
||||
<Input
|
||||
type="email"
|
||||
label="Email address"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
disabled={disableForm}
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
label="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
disabled={disableForm}
|
||||
required
|
||||
/>
|
||||
|
||||
<button type="submit" disabled={disableForm} className={styles.button}>
|
||||
{isLoading ? <Spinner size="sm" /> : 'Sign in'}
|
||||
</button>
|
||||
|
||||
{isError ? <p className={styles['error-text']}>{error?.message}</p> : null}
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className={styles.text}>
|
||||
No account yet?{' '}
|
||||
<Link to="/sign-up" className={styles.link}>
|
||||
Sign up
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SignIn
|
||||
```
|
||||
|
||||
#### 3. Sign-out
|
||||
|
||||
Finally, to allow the users to sign out from the app, we can use the Nhost
|
||||
`useSignOut` hook:
|
||||
|
||||
```jsx title="src/components/Layout.js"
|
||||
import { useSignOut } from '@nhost/react'
|
||||
|
||||
const Layout = () => {
|
||||
const { signOut } = useSignOut()
|
||||
|
||||
const menuItems = [
|
||||
//..
|
||||
{
|
||||
label: 'Logout',
|
||||
onClick: signOut,
|
||||
icon: LogoutIcon
|
||||
}
|
||||
]
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
### Protect routes
|
||||
|
||||
Now that we have implemented authentication, we can easily decide who can access
|
||||
certain parts of our application.
|
||||
|
||||
In our case, we'll only allow authenticated users to have access to the `/` and
|
||||
`/profile` routes. All the other users should be redirected to the `/sign-in`
|
||||
page if they try to access those routes.
|
||||
|
||||
To do so, we can create a wrapper component (`ProtectedRoute`) to check the authentication status of the current user using the Nhost SDK:
|
||||
|
||||
```jsx title="src/components/ProtectedRoute.js"
|
||||
import styles from '../styles/components/ProtectedRoute.module.css'
|
||||
import { useAuthenticationStatus } from '@nhost/react'
|
||||
import { Navigate, useLocation } from 'react-router-dom'
|
||||
import Spinner from './Spinner'
|
||||
|
||||
const ProtectedRoute = ({ children }) => {
|
||||
const { isAuthenticated, isLoading } = useAuthenticationStatus()
|
||||
const location = useLocation()
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Spinner />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return <Navigate to="/sign-in" state={{ from: location }} replace />
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
export default ProtectedRoute
|
||||
```
|
||||
|
||||
Then, we can use a [layout route](https://reactrouter.com/docs/en/v6/getting-started/concepts#layout-routes) in our `App.js` file, to wrap the `ProtectedRoute` component around the routes we want to protect:
|
||||
|
||||
```jsx title="src/App.js"
|
||||
import ProtectedRoute from './components/ProtectedRoute'
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<NhostProvider nhost={nhost}>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="sign-up" element={<SignUp />} />
|
||||
<Route path="sign-in" element={<SignIn />} />
|
||||
<Route
|
||||
path="/"
|
||||
// highlight-start
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Layout />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
// highlight-end
|
||||
>
|
||||
<Route index element={<Dashboard />} />
|
||||
<Route path="profile" element={<Profile />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</NhostProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieve user data
|
||||
|
||||
Finally, let's display the information of the authenticated user throughout his
|
||||
dashboard to make the app more personalized.
|
||||
|
||||
Getting the current authenticated user data is quite easy. Indeed, we
|
||||
can use the `useUserData` hook provided by Nhost to do it.
|
||||
|
||||
So, open the `components/Layout.js` file and use this hook like so:
|
||||
|
||||
```js
|
||||
import { useUserData } from '@nhost/react'
|
||||
|
||||
const Layout = () => {
|
||||
const user = useUserData()
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
That's it! The JSX code for rendering the user data (email, display name, etc.)
|
||||
is already included in your components as part of the template you've
|
||||
bootstraped at the beginning of this guide.
|
||||
|
||||
### Update user data
|
||||
|
||||
Nhost provides a GraphQL API through Hasura so that we can query and mutate our
|
||||
data instantly.
|
||||
|
||||
In this tutorial, we'll use the
|
||||
[Apollo GraphQL client](https://www.apollographql.com/) for interacting with
|
||||
this GraphQL API.
|
||||
|
||||
So, start by installing the following dependencies:
|
||||
|
||||
<Tabs groupId="package-manager">
|
||||
<TabItem value="npm" label="npm" default>
|
||||
|
||||
```bash
|
||||
npm install @nhost/react-apollo @apollo/client
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn add @nhost/react-apollo @apollo/client
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Then, add the `NhostApolloProvider` from `@nhost/react-apollo` into your
|
||||
`App.js` file.
|
||||
|
||||
```jsx title="src/App.js"
|
||||
import { NhostApolloProvider } from '@nhost/react-apollo'
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<NhostProvider nhost={nhost}>
|
||||
<NhostApolloProvider nhost={nhost}>{/* ... */}</NhostApolloProvider>
|
||||
</NhostProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
From there, we can construct our GraphQL query and use the Apollo `useMutation`
|
||||
hook to execute that query when the user submits the form from the profile page:
|
||||
|
||||
```js title="src/pages/Profile.js"
|
||||
import { gql, useMutation } from '@apollo/client'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
||||
const UPDATE_USER_MUTATION = gql`
|
||||
mutation ($id: uuid!, $displayName: String!, $metadata: jsonb) {
|
||||
updateUser(pk_columns: { id: $id }, _set: { displayName: $displayName, metadata: $metadata }) {
|
||||
id
|
||||
displayName
|
||||
metadata
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const Profile = () => {
|
||||
const [mutateUser, { loading: updatingProfile }] = useMutation(UPDATE_USER_MUTATION)
|
||||
|
||||
const updateUserProfile = async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
try {
|
||||
await mutateUser({
|
||||
variables: {
|
||||
id: user.id,
|
||||
displayName: `${firstName} ${lastName}`.trim(),
|
||||
metadata: {
|
||||
firstName,
|
||||
lastName
|
||||
}
|
||||
}
|
||||
})
|
||||
toast.success('Updated successfully', { id: 'updateProfile' })
|
||||
} catch (error) {
|
||||
toast.error('Unable to update profile', { id: 'updateProfile' })
|
||||
}
|
||||
}
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Finally, since Hasura has an **allow nothing by default** policy, and we haven't
|
||||
set any permissions yet, our GraphQL mutations would fail.
|
||||
|
||||
So, open the Hasura console from the **Data** tab of your project from [your Nhost dashboard](https://app.nhost.io/). Then, go to the **permissions** tab of the `users` table, type in `user` in the role
|
||||
cell, and click the edit icon on the `select` operation:
|
||||
|
||||

|
||||
|
||||
To restrict the user to read his own data only, specify a condition with the
|
||||
user's ID and the `X-Hasura-User-ID` session variable, which is passed with each
|
||||
requests.
|
||||
|
||||

|
||||
|
||||
Next, select the columns you'd like the users to have access to, and click
|
||||
**Save Permissions**.
|
||||
|
||||

|
||||
|
||||
Repeat the same steps on the `update` operation for the `user` role to allow
|
||||
users to update their `displayName` and `metadata` only.
|
||||
|
||||
Finally, to add caching, synchronizing, and updating server state in your React app, let's refactor the user data fetching using the Apollo client and our GraphQL API instead.
|
||||
|
||||
So, first add the following GraphQL query to retrieve the current user data from the `Layout` component:
|
||||
|
||||
```js title="src/components/Layout.js"
|
||||
import { gql } from '@apollo/client'
|
||||
|
||||
const GET_USER_QUERY = gql`
|
||||
query GetUser($id: uuid!) {
|
||||
user(id: $id) {
|
||||
id
|
||||
email
|
||||
displayName
|
||||
metadata
|
||||
avatarUrl
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const Layout = () => {
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Then, replace the `useUserData` hook with the `useUserId` hook to retrieve the current user's ID.
|
||||
|
||||
```js title="src/components/Layout.js"
|
||||
import { useUserId } from '@nhost/react'
|
||||
|
||||
const Layout = () => {
|
||||
const id = useUserId()
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Finally, we can run our GraphQL query using the `useQuery` hook and the current user's ID.
|
||||
|
||||
```jsx title="src/components/Layout.js"
|
||||
// highlight-next-line
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
|
||||
const Layout = () => {
|
||||
const id = useUserId()
|
||||
// highlight-start
|
||||
const { loading, error, data } = useQuery(GET_USER_QUERY, {
|
||||
variables: { id },
|
||||
skip: !id
|
||||
})
|
||||
const user = data?.user
|
||||
// highlight-end
|
||||
|
||||
//...
|
||||
|
||||
return (
|
||||
<div>
|
||||
<header>{/* ... */}</header>
|
||||
|
||||
<main className={styles.main}>
|
||||
<div className={styles['main-container']}>
|
||||
{/* highlight-start */}
|
||||
{error ? (
|
||||
<p>Something went wrong. Try to refresh the page.</p>
|
||||
) : !loading ? (
|
||||
<Outlet context={{ user }} />
|
||||
) : null}
|
||||
{/* highlight-end */}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You now have a fully functional React application. Congratulations!
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Did you enjoy Nhost? Give us a star ⭐ on [Github](https://github.com/nhost/nhost). Thank you!
|
||||
- Check out our more in-depth [examples](https://github.com/nhost/nhost/tree/main/examples).
|
||||
- Build your next app with [Nhost](https://app.nhost.io/)!
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user