Compare commits

...

40 Commits

Author SHA1 Message Date
github-actions[bot]
dd9dedc226 chore: update versions (#2086) 2023-06-30 10:12:55 +02:00
Hassan Ben Jobrane
5638a91240 Merge pull request #2080 from nhost/renovate/tsconfig-docusaurus-2.x
chore(deps): update dependency @tsconfig/docusaurus to v2
2023-06-29 18:19:00 +01:00
Hassan Ben Jobrane
cdefbdebee chore: remove unchanged packages from changeset 2023-06-29 17:09:27 +01:00
Hassan Ben Jobrane
923abd3655 chore: add changeset 2023-06-29 17:02:26 +01:00
renovate[bot]
ef28540f9a chore(deps): update dependency @tsconfig/docusaurus to v2 2023-06-29 15:10:11 +00:00
Szilárd Dóró
d54e4cdd4e fix(hasura-storage-js): allow using custom buckets for upload (#2085)
This PR is a fix for the [issue mentioned on our Discord
channel](https://discord.com/channels/552499021260914688/1123893547955933214/1123893547955933214).
It wasn't caused by the latest hasura-storage-js release.
2023-06-29 17:07:29 +02:00
David Barroso
4a00963602 feat(observability): added graph with restarts (#2084) 2023-06-29 13:57:37 +02:00
github-actions[bot]
7ea9b890c8 chore: update versions (#2083)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@0.17.19

### Patch Changes

-   f866120a6: fix(users): use the password length from the config

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-29 11:19:53 +02:00
Szilárd Dóró
f866120a65 fix(dashboard): use dynamic validation schema for password editing (#2082)
Fixes #2081
2023-06-29 10:52:40 +02:00
github-actions[bot]
472559276c chore: update versions (#2079)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


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

### Minor Changes

- 2cdb13b3e: fix(upload): allow specifying `id` and `name` only when not
using `form-data`

## @nhost/apollo@5.2.13

### Patch Changes

-   @nhost/nhost-js@2.2.11

## @nhost/react-apollo@5.0.30

### Patch Changes

-   @nhost/apollo@5.2.13
-   @nhost/react@2.0.26

## @nhost/react-urql@2.0.27

### Patch Changes

-   @nhost/react@2.0.26

## @nhost/nextjs@1.13.32

### Patch Changes

-   @nhost/react@2.0.26

## @nhost/nhost-js@2.2.11

### Patch Changes

-   Updated dependencies [2cdb13b3e]
    -   @nhost/hasura-storage-js@2.2.0

## @nhost/react@2.0.26

### Patch Changes

-   @nhost/nhost-js@2.2.11

## @nhost/vue@1.13.31

### Patch Changes

-   @nhost/nhost-js@2.2.11

## @nhost/dashboard@0.17.18

### Patch Changes

-   @nhost/react-apollo@5.0.30
-   @nhost/nextjs@1.13.32

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

### Patch Changes

- 2cdb13b3e: fix(upload): allow specifying `id` and `name` only when not
using `form-data`
    -   @nhost/nhost-js@2.2.11

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-27 16:50:56 +02:00
Szilárd Dóró
2cdb13b3ef fix(hasura-storage-js): streamline file upload API (#2072)
Fixes #2071

This PR changes the API of the `hasura-storage-js` SDK slightly.

Before:
```ts
const formData = new FormData();

// first file
formData.append('file[]', '<file>');

// second file
formData.append('file[]', '<file>');

const { fileMetadata, error } = await nhost.storage.upload({
  formData,
  id: '<custom-uuid>', // ID doesn't make sense anymore when uploading multiple files
  name: '<custom-name>', // Name doesn't make sense anymore when uploading multiple files
});
```

Now:
```ts
const formData = new FormData();

// first file
formData.append('file[]', '<file>', '<custom-name>');
formData.append('metadata[]', JSON.stringify({ id: '<custom-uuid>' }))

// second file
formData.append('file[]', '<file>', '<custom-name>');
formData.append('metadata[]', JSON.stringify({ id: '<custom-uuid>' }))

const { fileMetadata, error } = await nhost.storage.upload({ formData });

// Access the metadata of upload files via fileMetadata.processedFiles
```

The `id` and `name` attributes can only be specified if you want to
upload a single file:

```ts
const file = event.target.files[0];

const { fileMetadata, error } = await nhost.storage.upload({
  file,
  id: '<custom-id>',
  name: '<custom-name>',
});

// Access the metadata of the upload file via fileMetadata
```
2023-06-27 16:20:17 +02:00
github-actions[bot]
a41124c5e0 chore: update versions (#2077)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/google-translation@0.0.6

### Patch Changes

-   a5305e6b5: docs: update old URLs to the new format

## @nhost/dashboard@0.17.17

### Patch Changes

-   ea7b102c0: fix(pat): highlight expired tokens

## @nhost/docs@0.3.4

### Patch Changes

-   a5305e6b5: docs: update old URLs to the new format

## @nhost-examples/seed-data-storage@0.0.4

### Patch Changes

-   a5305e6b5: docs: update old URLs to the new format

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-06-27 12:59:50 +02:00
Hassan Ben Jobrane
6ecffa81ae Merge pull request #2078 from nhost/fix/highlight-expired-tokens
fix(pat): highlight expired tokens
2023-06-27 11:46:19 +01:00
Hassan Ben Jobrane
ea7b102c07 chroe: add changeset 2023-06-27 11:31:03 +01:00
Hassan Ben Jobrane
e9daf92830 chore: fix code formatting 2023-06-27 10:09:29 +01:00
Hassan Ben Jobrane
9e4ad76e7f style: use darker color 2023-06-27 10:00:34 +01:00
Szilárd Dóró
0fd65db563 chore(dashboard): extend readme (#2076) 2023-06-27 09:19:07 +02:00
Hassan Ben Jobrane
146fbb84b9 fix: highlight expired tokens 2023-06-26 17:57:48 +01:00
Szilárd Dóró
b51c18fedb Merge pull request #2075 from nhost/docs/fix-old-urls
fix(docs): use modern URLs
2023-06-26 16:43:12 +02:00
Szilárd Dóró
a5305e6b56 chore: add changeset 2023-06-26 16:31:36 +02:00
Szilárd Dóró
aa88ef2e5c fix(docs): use correct functions URL 2023-06-26 16:08:58 +02:00
Szilárd Dóró
ee6b3c9ac8 fix(docs): use modern URLs 2023-06-26 16:06:49 +02:00
Szilárd Dóró
79fd86acc5 Merge pull request #2074 from nhost/fix/e2e-timeout
fix(ci): timeout long running e2e tests
2023-06-26 15:43:03 +02:00
Szilárd Dóró
c2cbeddcb8 fix(ci): timeout long running e2e tests 2023-06-26 15:34:36 +02:00
Szilárd Dóró
62b2de59d4 Merge pull request #2073 from nhost/changeset-release/main
chore: update versions
2023-06-25 17:50:18 +02:00
github-actions[bot]
2a760593db chore: update versions 2023-06-25 15:34:14 +00:00
Szilárd Dóró
9288873ce8 Merge pull request #2070 from nhost/renovate/react-monorepo
chore(deps): update react monorepo
2023-06-25 17:32:26 +02:00
Szilárd Dóró
47014be8e3 Merge pull request #2065 from nhost/renovate/turbo-monorepo
chore(deps): update dependency turbo to v1.10.6
2023-06-25 15:37:07 +02:00
Szilárd Dóró
49719f7a84 fix: don't break build 2023-06-25 15:23:22 +02:00
Szilárd Dóró
b3b64a3b74 chore: sync versions and add changeset 2023-06-25 15:21:13 +02:00
Szilárd Dóró
3a56c12df4 chore(dashboard): bump turbo to v1.10.6 2023-06-25 15:15:46 +02:00
renovate[bot]
5b15a4f235 chore(deps): update react monorepo 2023-06-25 13:13:47 +00:00
renovate[bot]
83303017c3 chore(deps): update dependency turbo to v1.10.6 2023-06-25 13:12:39 +00:00
Szilárd Dóró
e0739a5883 Merge pull request #2067 from nhost/renovate/graphiql-react-0.x
fix(deps): update dependency @graphiql/react to ^0.18.0
2023-06-25 15:10:36 +02:00
Szilárd Dóró
0a5a841cc8 fix: don't break builds 2023-06-25 14:57:31 +02:00
Szilárd Dóró
3309835f06 chore: revert PNPM version in flake.nix 2023-06-25 14:05:35 +02:00
Szilárd Dóró
32b221f944 chore: add changeset 2023-06-25 14:02:47 +02:00
renovate[bot]
e8a99badb8 fix(deps): update dependency @graphiql/react to ^0.18.0 2023-06-25 11:54:28 +00:00
Szilárd Dóró
1ea6e01963 Merge pull request #2066 from nhost/renovate/tj-actions-changed-files-37.x
chore(deps): update tj-actions/changed-files action to v37
2023-06-25 13:54:11 +02:00
renovate[bot]
09257fbfb2 chore(deps): update tj-actions/changed-files action to v37 2023-06-24 17:15:19 +00:00
107 changed files with 2536 additions and 771 deletions

View File

@@ -43,7 +43,7 @@ jobs:
BUILD: 'all'
- name: Check if the pnpm lockfile changed
id: changed-lockfile
uses: tj-actions/changed-files@v36
uses: tj-actions/changed-files@v37
with:
files: pnpm-lock.yaml
# * Determine a pnpm filter argument for packages that have been modified.
@@ -146,6 +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: 7
run: pnpm --filter="${{ matrix.package.name }}" run e2e
- id: file-name
if: ${{ failure() }}

View File

@@ -1,5 +1,42 @@
# @nhost/dashboard
## 0.17.20
### Patch Changes
- @nhost/react-apollo@5.0.31
- @nhost/nextjs@1.13.33
## 0.17.19
### Patch Changes
- f866120a6: fix(users): use the password length from the config
## 0.17.18
### Patch Changes
- @nhost/react-apollo@5.0.30
- @nhost/nextjs@1.13.32
## 0.17.17
### Patch Changes
- ea7b102c0: fix(pat): highlight expired tokens
## 0.17.16
### Patch Changes
- b3b64a3b7: chore(deps): bump `@types/react` to `v18.2.14` and `@types/react-dom` to `v18.2.6`
- 32b221f94: chore(deps): bump `graphiql` to `v3`
- 3a56c12df: chore(deps): bump `turbo` to `v1.10.6`
- Updated dependencies [b3b64a3b7]
- @nhost/react-apollo@5.0.29
- @nhost/nextjs@1.13.31
## 0.17.15
### Patch Changes

View File

@@ -3,7 +3,7 @@ RUN apk add --no-cache libc6-compat
RUN apk update
WORKDIR /app
RUN yarn global add turbo@1.10.5
RUN yarn global add turbo@1.10.6
COPY . .
RUN turbo prune --scope="@nhost/dashboard" --docker

View File

@@ -9,13 +9,19 @@ First, install the dependencies:
pnpm install
```
Then, run the development server:
Then, build the packages that are used by the Nhost Dashboard:
```bash
pnpm -w build
```
Finally, run the development server:
```bash
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
Open [http://localhost:3000](http://localhost:3000) to see the result in your browser.
## Environment

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "0.17.15",
"version": "0.17.20",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -26,7 +26,7 @@
"@emotion/styled": "^11.10.5",
"@fontsource/inter": "^5.0.0",
"@fontsource/roboto-mono": "^5.0.0",
"@graphiql/react": "^0.17.0",
"@graphiql/react": "^0.18.0",
"@graphiql/toolkit": "^0.8.2",
"@headlessui/react": "^1.6.5",
"@heroicons/react": "^1.0.6",
@@ -49,7 +49,7 @@
"clsx": "^1.2.1",
"date-fns": "^2.29.3",
"generate-password": "^1.7.0",
"graphiql": "^2.4.0",
"graphiql": "^3.0.0",
"graphql": "^16.6.0",
"graphql-request": "^6.0.0",
"graphql-tag": "^2.12.6",
@@ -101,11 +101,12 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/bcryptjs": "^2.4.2",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "^16.11.7",
"@types/pluralize": "^0.0.29",
"@types/react": "18.2.12",
"@types/react-dom": "18.2.5",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@types/react-table": "^7.7.12",
"@types/testing-library__jest-dom": "^5.14.5",
"@types/validator": "^13.7.10",

View File

@@ -1,11 +1,18 @@
import { styled } from '@mui/material';
import { textClasses } from '@/components/ui/v2/Text';
import { getTypographyUtilityClass, styled } from '@mui/material';
import type { ListItemTextProps as MaterialListItemTextProps } from '@mui/material/ListItemText';
import MaterialListItemText, {
listItemTextClasses,
listItemTextClasses as materialListItemTextClasses,
} from '@mui/material/ListItemText';
import clsx from 'clsx';
export interface ListItemTextProps extends MaterialListItemTextProps {}
const listItemTextClasses = {
...materialListItemTextClasses,
warning: getTypographyUtilityClass('colorWarning'),
};
const StyledListItemText = styled(MaterialListItemText)(({ theme }) => ({
color: theme.palette.text.primary,
display: 'grid',
@@ -16,6 +23,9 @@ const StyledListItemText = styled(MaterialListItemText)(({ theme }) => ({
[`&.${listItemTextClasses.root}`]: {
margin: 0,
},
[`&.${listItemTextClasses.warning}`]: {
color: theme.palette.warning.dark,
},
[`& > .${listItemTextClasses.primary}`]: {
fontWeight: 500,
textOverflow: 'ellipsis',
@@ -29,8 +39,23 @@ const StyledListItemText = styled(MaterialListItemText)(({ theme }) => ({
},
}));
function ListItemText({ children, ...props }: ListItemTextProps) {
return <StyledListItemText {...props}>{children}</StyledListItemText>;
function ListItemText({
children,
color = 'primary',
className,
...props
}: ListItemTextProps) {
return (
<StyledListItemText
className={clsx(
color === 'warning' && textClasses.colorWarning,
className,
)}
{...props}
>
{children}
</StyledListItemText>
);
}
ListItemText.displayName = 'NhostListItemText';

View File

@@ -18,7 +18,7 @@ export type TextProps<
*
* @default 'primary'
*/
color?: 'primary' | 'secondary' | 'disabled' | 'error';
color?: 'primary' | 'secondary' | 'disabled' | 'error' | 'warning';
/**
* The component used for the root node.
*/
@@ -31,6 +31,7 @@ const textClasses = {
colorSecondary: getTypographyUtilityClass('colorSecondary'),
colorDisabled: getTypographyUtilityClass('colorDisabled'),
colorError: getTypographyUtilityClass('colorError'),
colorWarning: getTypographyUtilityClass('colorWarning'),
};
const StyledTypography = styled(MaterialTypography)<TextProps>(({ theme }) => ({
@@ -50,6 +51,9 @@ const StyledTypography = styled(MaterialTypography)<TextProps>(({ theme }) => ({
[`&.${textClasses.colorError}`]: {
color: theme.palette.error.main,
},
[`&.${textClasses.colorWarning}`]: {
color: theme.palette.warning.dark,
},
}));
function Text<
@@ -70,6 +74,7 @@ function Text<
color === 'secondary' && textClasses.colorSecondary,
color === 'disabled' && textClasses.colorDisabled,
color === 'error' && textClasses.colorError,
color === 'warning' && textClasses.colorWarning,
className,
)}
{...props}

View File

@@ -0,0 +1,40 @@
import type { IconProps } from '@/components/ui/v2/icons';
import { SvgIcon } from '@/components/ui/v2/icons/SvgIcon';
import type { ForwardedRef } from 'react';
import { forwardRef } from 'react';
function WarningIcon(props: IconProps, ref: ForwardedRef<SVGSVGElement>) {
return (
<SvgIcon
width="16"
height="16"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
aria-label="Warning"
ref={ref}
{...props}
>
<path
d="M8 5.5V9.5"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
<path
d="M7.135 2.49904L1.63648 11.9986C1.5485 12.1506 1.5021 12.3231 1.50195 12.4987C1.50181 12.6743 1.54792 12.8469 1.63565 12.999C1.72338 13.1512 1.84964 13.2776 2.00172 13.3654C2.15379 13.4533 2.32633 13.4995 2.50196 13.4995H13.499C13.6746 13.4995 13.8472 13.4533 13.9992 13.3654C14.1513 13.2776 14.2776 13.1512 14.3653 12.999C14.453 12.8469 14.4991 12.6743 14.499 12.4987C14.4988 12.3231 14.4524 12.1506 14.3645 11.9986L8.86594 2.49904C8.7781 2.34728 8.6519 2.22129 8.49999 2.1337C8.34809 2.04611 8.17582 2 8.00047 2C7.82512 2 7.65285 2.04611 7.50095 2.1337C7.34904 2.22129 7.22284 2.34728 7.135 2.49904V2.49904Z"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
<path
d="M8 12C8.41421 12 8.75 11.6642 8.75 11.25C8.75 10.8358 8.41421 10.5 8 10.5C7.58579 10.5 7.25 10.8358 7.25 11.25C7.25 11.6642 7.58579 12 8 12Z"
fill="currentColor"
/>
</SvgIcon>
);
}
WarningIcon.displayName = 'NhostWarningIcon';
export default forwardRef(WarningIcon);

View File

@@ -0,0 +1 @@
export { default as WarningIcon } from './WarningIcon';

View File

@@ -9,9 +9,11 @@ import { Dropdown } from '@/components/ui/v2/Dropdown';
import { IconButton } from '@/components/ui/v2/IconButton';
import { DotsVerticalIcon } from '@/components/ui/v2/icons/DotsVerticalIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { WarningIcon } from '@/components/ui/v2/icons/WarningIcon';
import { List } from '@/components/ui/v2/List';
import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { CreatePATForm } from '@/features/account/settings/components/CreatePATForm';
import { getToastStyleProps } from '@/utils/constants/settings';
import { getServerError } from '@/utils/getServerError';
@@ -133,69 +135,91 @@ export default function PATSettings() {
<Box className="grid grid-flow-row gap-2">
{availablePersonalAccessTokens.length > 0 && (
<List>
{availablePersonalAccessTokens.map((pat, index) => (
<Fragment key={pat.id}>
<ListItem.Root
className="grid grid-cols-3 gap-2 px-4 pr-12"
secondaryAction={
<Dropdown.Root>
<Dropdown.Trigger
asChild
hideChevron
className="absolute right-4 top-1/2 -translate-y-1/2"
>
<IconButton
variant="borderless"
color="secondary"
disabled={maintenanceActive}
aria-label={`More options for ${pat.name}`}
{availablePersonalAccessTokens.map((pat, index) => {
const tokenHasExpired = new Date(pat.expiresAt) < new Date();
return (
<Fragment key={pat.id}>
<ListItem.Root
className="grid grid-cols-3 gap-2 px-4 pr-12"
secondaryAction={
<Dropdown.Root>
<Dropdown.Trigger
asChild
hideChevron
className="absolute right-4 top-1/2 -translate-y-1/2"
>
<DotsVerticalIcon />
</IconButton>
</Dropdown.Trigger>
<IconButton
variant="borderless"
color="secondary"
disabled={maintenanceActive}
aria-label={`More options for ${pat.name}`}
>
<DotsVerticalIcon />
</IconButton>
</Dropdown.Trigger>
<Dropdown.Content
menu
PaperProps={{ className: 'w-32' }}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<Dropdown.Item onClick={() => handleConfirmDelete(pat)}>
<Text className="font-medium" color="error">
Delete
</Text>
</Dropdown.Item>
</Dropdown.Content>
</Dropdown.Root>
}
>
<ListItem.Text className="truncate">{pat.name}</ListItem.Text>
<Dropdown.Content
menu
PaperProps={{ className: 'w-32' }}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<Dropdown.Item
onClick={() => handleConfirmDelete(pat)}
>
<Text className="font-medium" color="error">
Delete
</Text>
</Dropdown.Item>
</Dropdown.Content>
</Dropdown.Root>
}
>
<ListItem.Text
className="truncate"
color={tokenHasExpired ? 'warning' : 'primary'}
>
<span className="mr-2">{pat.name}</span>
{tokenHasExpired && (
<Tooltip title="This personal access token is expired.">
<WarningIcon className="h-4 w-4" />
</Tooltip>
)}
</ListItem.Text>
<Text className="truncate">
{new Date(pat.expiresAt).toLocaleDateString()}
</Text>
<Text
className="truncate"
color={tokenHasExpired ? 'warning' : 'primary'}
>
{new Date(pat.expiresAt).toLocaleDateString()}
</Text>
<Text className="truncate">
{new Date(pat.createdAt).toLocaleDateString()}
</Text>
</ListItem.Root>
<Text
className="truncate"
color={tokenHasExpired ? 'warning' : 'primary'}
>
{new Date(pat.createdAt).toLocaleDateString()}
</Text>
</ListItem.Root>
<Divider
component="li"
className={twMerge(
index === availablePersonalAccessTokens.length - 1
? '!mt-4'
: '!my-4',
)}
/>
</Fragment>
))}
<Divider
component="li"
className={twMerge(
index === availablePersonalAccessTokens.length - 1
? '!mt-4'
: '!my-4',
)}
/>
</Fragment>
);
})}
</List>
)}

View File

@@ -3,12 +3,16 @@ import { Form } from '@/components/form/Form';
import { Alert } from '@/components/ui/v2/Alert';
import { Button } from '@/components/ui/v2/Button';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
import type { DialogFormProps } from '@/types/common';
import { getToastStyleProps } from '@/utils/constants/settings';
import { getServerError } from '@/utils/getServerError';
import type { RemoteAppGetUsersQuery } from '@/utils/__generated__/graphql';
import { useUpdateRemoteAppUserMutation } from '@/utils/__generated__/graphql';
import {
useGetSignInMethodsQuery,
useUpdateRemoteAppUserMutation,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import bcrypt from 'bcryptjs';
import { useState } from 'react';
@@ -27,19 +31,6 @@ export interface EditUserPasswordFormProps extends DialogFormProps {
user: RemoteAppGetUsersQuery['users'][0];
}
export const validationSchema = Yup.object({
password: Yup.string()
.label('Users Password')
.min(8, 'Password must be at least 8 characters long.')
.required('This field is required.'),
cpassword: Yup.string()
.required('Confirm Password is required')
.min(8, 'Password must be at least 8 characters long.')
.oneOf([Yup.ref('password')], 'Passwords do not match'),
});
export type EditUserPasswordFormValues = Yup.InferType<typeof validationSchema>;
export default function EditUserPasswordForm({
onCancel,
user,
@@ -49,26 +40,52 @@ export default function EditUserPasswordForm({
client: remoteProjectGQLClient,
});
const { closeDialog } = useDialog();
const { currentProject } = useCurrentWorkspaceAndProject();
const { data } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
skip: !currentProject?.id,
});
const passwordMinLength =
data?.config?.auth?.method?.emailPassword?.passwordMinLength || 1;
const validationSchema = Yup.object({
password: Yup.string()
.label('Password')
.min(
passwordMinLength,
`Password must be at least ${passwordMinLength} characters long.`,
)
.required('This field is required.'),
cpassword: Yup.string()
.label('Password Confirmation')
.min(
passwordMinLength,
`Password must be at least ${passwordMinLength} characters long.`,
)
.oneOf([Yup.ref('password')], 'Passwords do not match')
.required('This field is required.'),
});
const [editUserPasswordFormError, setEditUserPasswordFormError] =
useState<Error | null>(null);
const form = useForm<EditUserPasswordFormValues>({
const form = useForm<Yup.InferType<typeof validationSchema>>({
defaultValues: {},
reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema),
});
const handleSubmit = async ({ password }: EditUserPasswordFormValues) => {
const handleSubmit = async ({
password,
}: Yup.InferType<typeof validationSchema>) => {
setEditUserPasswordFormError(null);
const passwordHash = await bcrypt.hash(password, 10);
const updateUserPasswordPromise = updateUser({
variables: {
id: user.id,
user: {
passwordHash,
},
user: { passwordHash },
},
client: remoteProjectGQLClient,
});

View File

@@ -1,5 +1,17 @@
# @nhost/docs
## 0.3.5
### Patch Changes
- 923abd365: chore(deps): update dependency @tsconfig/docusaurus to v2
## 0.3.4
### Patch Changes
- a5305e6b5: docs: update old URLs to the new format
## 0.3.3
### Patch Changes

View File

@@ -63,8 +63,8 @@ Follow this guide to sign in users with Apple.
- Click the checkbox to enable "Sign in with Apple".
- Click **Configure** next to "Sign in with Apple".
- Make sure your newly created Bundle ID is selected under Primary App ID.
- Add your base auth domain under "Domains and Subdomains". E.g. `<subdomain>.nhost.run`.
- Add the full callback URL under "Return URLs". E.g. `https://<subdomain>.nhost.run/v1/auth/signin/provider/apple/callback`.
- Add your base auth domain under "Domains and Subdomains". E.g. `<subdomain>.auth.<region>.nhost.run`.
- Add the full callback URL under "Return URLs". E.g. `https://<subdomain>.auth.<region>.nhost.run/v1/signin/provider/apple/callback`.
- Click **Next**.
- Click **Done**.
- Click **Continue** in the top right corner.

View File

@@ -48,7 +48,7 @@ Learn more about [`upload()`](/reference/javascript/storage/upload).
<TabItem value="http" label="HTTP" default>
```http
POST /v1/storage/files HTTP/1.1
POST https://local.storage.nhost.run/v1/files HTTP/1.1
```
</TabItem>
@@ -80,7 +80,7 @@ Learn more about [`getPublicUrl()`](/reference/javascript/storage/get-public-url
<TabItem value="http" label="HTTP" default>
```http
GET /v1/storage/files/{file_id} HTTP/1.1
GET https://local.storage.nhost.run/v1/files/{file_id} HTTP/1.1
```
</TabItem>
@@ -109,7 +109,7 @@ Learn more about [`getPresignedUrl()`](/reference/javascript/storage/get-presign
<TabItem value="http" label="HTTP" default>
```http
GET /v1/storage/files/{file_id}/presignedurl HTTP/1.1
GET https://local.storage.nhost.run/v1/files/{file_id}/presignedurl HTTP/1.1
```
</TabItem>
@@ -132,7 +132,7 @@ Learn more about [`delete()`](/reference/javascript/storage/delete).
<TabItem value="http" label="HTTP" default>
```http
DELETE /v1/storage/files/{file_id} HTTP/1.1
DELETE https://local.storage.nhost.run/v1/files/{file_id} HTTP/1.1
```
</TabItem>
@@ -193,7 +193,7 @@ Image Transformation works on both public and pre-signed URLs.
**Example**: Transform an image to 500px width (`?w=500`):
```text
https://[subdomain].nhost.run/v1/storage/files/08e6fa32-0880-4d0e-a832-278198acb363?w=500
https://[subdomain].storage.[region].nhost.run/v1/files/08e6fa32-0880-4d0e-a832-278198acb363?w=500
```
## Example: CRM System

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/docs",
"version": "0.3.3",
"version": "0.3.5",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
@@ -31,7 +31,7 @@
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.4.1",
"@tsconfig/docusaurus": "^1.0.6",
"@tsconfig/docusaurus": "^2.0.0",
"typescript": "^4.8.4"
},
"browserslist": {

View File

@@ -1,5 +1,14 @@
# @nhost-examples/codegen-react-apollo
## 0.1.10
### Patch Changes
- b3b64a3b7: chore(deps): bump `@types/react` to `v18.2.14` and `@types/react-dom` to `v18.2.6`
- Updated dependencies [b3b64a3b7]
- @nhost/react-apollo@5.0.29
- @nhost/react@2.0.25
## 0.1.9
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/codegen-react-apollo",
"version": "0.1.9",
"version": "0.1.10",
"private": true,
"scripts": {
"codegen": "graphql-codegen",
@@ -29,8 +29,8 @@
"@graphql-typed-document-node/core": "^3.1.1",
"@tailwindcss/forms": "^0.5.3",
"@types/node": "^18.11.9",
"@types/react": "^18.0.34",
"@types/react-dom": "^18.0.11",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@vitejs/plugin-react": "^3.0.0",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.19",

View File

@@ -1,5 +1,13 @@
# @nhost-examples/codegen-react-query
## 0.1.11
### Patch Changes
- b3b64a3b7: chore(deps): bump `@types/react` to `v18.2.14` and `@types/react-dom` to `v18.2.6`
- Updated dependencies [b3b64a3b7]
- @nhost/react@2.0.25
## 0.1.10
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/codegen-react-query",
"version": "0.1.10",
"version": "0.1.11",
"private": true,
"scripts": {
"codegen": "graphql-codegen",
@@ -30,8 +30,8 @@
"@graphql-typed-document-node/core": "^3.1.1",
"@tailwindcss/forms": "^0.5.3",
"@types/node": "^16.11.56",
"@types/react": "^18.0.34",
"@types/react-dom": "^18.0.11",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"autoprefixer": "^10.4.13",
"eslint": "^8.23.0",
"postcss": "^8.4.20",

View File

@@ -1,5 +1,14 @@
# @nhost-examples/react-urql
## 0.0.7
### Patch Changes
- b3b64a3b7: chore(deps): bump `@types/react` to `v18.2.14` and `@types/react-dom` to `v18.2.6`
- Updated dependencies [b3b64a3b7]
- @nhost/react-urql@2.0.26
- @nhost/react@2.0.25
## 0.0.6
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/codegen-react-urql",
"private": true,
"version": "0.0.6",
"version": "0.0.7",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
@@ -23,8 +23,8 @@
"@graphql-typed-document-node/core": "^3.1.1",
"@tailwindcss/forms": "^0.5.3",
"@types/node": "^16.11.7",
"@types/react": "^18.0.34",
"@types/react-dom": "^18.0.11",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@vitejs/plugin-react": "^3.0.0",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.19",

View File

@@ -1,5 +1,15 @@
# @nhost-examples/nextjs
## 0.1.12
### Patch Changes
- b3b64a3b7: chore(deps): bump `@types/react` to `v18.2.14` and `@types/react-dom` to `v18.2.6`
- Updated dependencies [b3b64a3b7]
- @nhost/react-apollo@5.0.29
- @nhost/react@2.0.25
- @nhost/nextjs@1.13.31
## 0.1.11
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/nextjs",
"version": "0.1.11",
"version": "0.1.12",
"private": true,
"scripts": {
"dev": "next dev",
@@ -32,7 +32,7 @@
"devDependencies": {
"@next/bundle-analyzer": "^12.2.5",
"@types/node": "^16.11.7",
"@types/react": "18.0.34",
"@types/react": "^18.2.14",
"@xstate/inspect": "^0.6.2",
"eslint-config-next": "12.0.10",
"typescript": "^4.8.2",

View File

@@ -1 +1,3 @@
.secrets.nhost
.secrets
.nhost
cat.jpg

View File

@@ -1,5 +1,19 @@
# @nhost-examples/node-storage
## 0.0.4
### Patch Changes
- d54e4cdd4: fix(buckets): allow using custom buckets for upload
- @nhost/nhost-js@2.2.12
## 0.0.3
### Patch Changes
- 2cdb13b3e: fix(upload): allow specifying `id` and `name` only when not using `form-data`
- @nhost/nhost-js@2.2.11
## 0.0.2
### Patch Changes

View File

@@ -26,5 +26,4 @@ You can use the `.env.example` file as a starting point.
pnpm start
```
The example will download a file from a public URL and upload it to your Nhost
Storage bucket.
The example will run a few upload operations and then exit.

View File

@@ -0,0 +1,6 @@
actions: []
custom_types:
enums: []
input_objects: []
objects: []
scalars: []

View File

@@ -0,0 +1 @@
[]

View File

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

View File

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

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,14 @@
- name: default
kind: postgres
configuration:
connection_info:
database_url:
from_env: HASURA_GRAPHQL_DATABASE_URL
isolation_level: read-committed
pool_settings:
connection_lifetime: 600
idle_timeout: 180
max_connections: 50
retries: 1
use_prepared_statements: true
tables: "!include default/tables/tables.yaml"

View File

@@ -0,0 +1,23 @@
table:
name: provider_requests
schema: auth
configuration:
column_config:
id:
custom_name: id
options:
custom_name: options
custom_column_names:
id: id
options: options
custom_name: authProviderRequests
custom_root_fields:
delete: deleteAuthProviderRequests
delete_by_pk: deleteAuthProviderRequest
insert: insertAuthProviderRequests
insert_one: insertAuthProviderRequest
select: authProviderRequests
select_aggregate: authProviderRequestsAggregate
select_by_pk: authProviderRequest
update: updateAuthProviderRequests
update_by_pk: updateAuthProviderRequest

View File

@@ -0,0 +1,28 @@
table:
name: providers
schema: auth
configuration:
column_config:
id:
custom_name: id
custom_column_names:
id: id
custom_name: authProviders
custom_root_fields:
delete: deleteAuthProviders
delete_by_pk: deleteAuthProvider
insert: insertAuthProviders
insert_one: insertAuthProvider
select: authProviders
select_aggregate: authProvidersAggregate
select_by_pk: authProvider
update: updateAuthProviders
update_by_pk: updateAuthProvider
array_relationships:
- name: userProviders
using:
foreign_key_constraint_on:
column: provider_id
table:
name: user_providers
schema: auth

View File

@@ -0,0 +1,26 @@
table:
name: refresh_token_types
schema: auth
is_enum: true
configuration:
column_config: {}
custom_column_names: {}
custom_name: authRefreshTokenTypes
custom_root_fields:
delete: deleteAuthRefreshTokenTypes
delete_by_pk: deleteAuthRefreshTokenType
insert: insertAuthRefreshTokenTypes
insert_one: insertAuthRefreshTokenType
select: authRefreshTokenTypes
select_aggregate: authRefreshTokenTypesAggregate
select_by_pk: authRefreshTokenType
update: updateAuthRefreshTokenTypes
update_by_pk: updateAuthRefreshTokenType
array_relationships:
- name: refreshTokens
using:
foreign_key_constraint_on:
column: type
table:
name: refresh_tokens
schema: auth

View File

@@ -0,0 +1,55 @@
table:
name: refresh_tokens
schema: auth
configuration:
column_config:
created_at:
custom_name: createdAt
expires_at:
custom_name: expiresAt
refresh_token_hash:
custom_name: refreshTokenHash
user_id:
custom_name: userId
custom_column_names:
created_at: createdAt
expires_at: expiresAt
refresh_token_hash: refreshTokenHash
user_id: userId
custom_name: authRefreshTokens
custom_root_fields:
delete: deleteAuthRefreshTokens
delete_by_pk: deleteAuthRefreshToken
insert: insertAuthRefreshTokens
insert_one: insertAuthRefreshToken
select: authRefreshTokens
select_aggregate: authRefreshTokensAggregate
select_by_pk: authRefreshToken
update: updateAuthRefreshTokens
update_by_pk: updateAuthRefreshToken
object_relationships:
- name: user
using:
foreign_key_constraint_on: user_id
select_permissions:
- role: user
permission:
columns:
- id
- created_at
- expires_at
- metadata
- type
- user_id
filter:
user_id:
_eq: X-Hasura-User-Id
delete_permissions:
- role: user
permission:
filter:
_and:
- user_id:
_eq: X-Hasura-User-Id
- type:
_eq: pat

View File

@@ -0,0 +1,35 @@
table:
name: roles
schema: auth
configuration:
column_config:
role:
custom_name: role
custom_column_names:
role: role
custom_name: authRoles
custom_root_fields:
delete: deleteAuthRoles
delete_by_pk: deleteAuthRole
insert: insertAuthRoles
insert_one: insertAuthRole
select: authRoles
select_aggregate: authRolesAggregate
select_by_pk: authRole
update: updateAuthRoles
update_by_pk: updateAuthRole
array_relationships:
- name: userRoles
using:
foreign_key_constraint_on:
column: role
table:
name: user_roles
schema: auth
- name: usersByDefaultRole
using:
foreign_key_constraint_on:
column: default_role
table:
name: users
schema: auth

View File

@@ -0,0 +1,48 @@
table:
name: user_providers
schema: auth
configuration:
column_config:
access_token:
custom_name: accessToken
created_at:
custom_name: createdAt
id:
custom_name: id
provider_id:
custom_name: providerId
provider_user_id:
custom_name: providerUserId
refresh_token:
custom_name: refreshToken
updated_at:
custom_name: updatedAt
user_id:
custom_name: userId
custom_column_names:
access_token: accessToken
created_at: createdAt
id: id
provider_id: providerId
provider_user_id: providerUserId
refresh_token: refreshToken
updated_at: updatedAt
user_id: userId
custom_name: authUserProviders
custom_root_fields:
delete: deleteAuthUserProviders
delete_by_pk: deleteAuthUserProvider
insert: insertAuthUserProviders
insert_one: insertAuthUserProvider
select: authUserProviders
select_aggregate: authUserProvidersAggregate
select_by_pk: authUserProvider
update: updateAuthUserProviders
update_by_pk: updateAuthUserProvider
object_relationships:
- name: provider
using:
foreign_key_constraint_on: provider_id
- name: user
using:
foreign_key_constraint_on: user_id

View File

@@ -0,0 +1,36 @@
table:
name: user_roles
schema: auth
configuration:
column_config:
created_at:
custom_name: createdAt
id:
custom_name: id
role:
custom_name: role
user_id:
custom_name: userId
custom_column_names:
created_at: createdAt
id: id
role: role
user_id: userId
custom_name: authUserRoles
custom_root_fields:
delete: deleteAuthUserRoles
delete_by_pk: deleteAuthUserRole
insert: insertAuthUserRoles
insert_one: insertAuthUserRole
select: authUserRoles
select_aggregate: authUserRolesAggregate
select_by_pk: authUserRole
update: updateAuthUserRoles
update_by_pk: updateAuthUserRole
object_relationships:
- name: roleByRole
using:
foreign_key_constraint_on: role
- name: user
using:
foreign_key_constraint_on: user_id

View File

@@ -0,0 +1,33 @@
table:
name: user_security_keys
schema: auth
configuration:
column_config:
credential_id:
custom_name: credentialId
credential_public_key:
custom_name: credentialPublicKey
id:
custom_name: id
user_id:
custom_name: userId
custom_column_names:
credential_id: credentialId
credential_public_key: credentialPublicKey
id: id
user_id: userId
custom_name: authUserSecurityKeys
custom_root_fields:
delete: deleteAuthUserSecurityKeys
delete_by_pk: deleteAuthUserSecurityKey
insert: insertAuthUserSecurityKeys
insert_one: insertAuthUserSecurityKey
select: authUserSecurityKeys
select_aggregate: authUserSecurityKeysAggregate
select_by_pk: authUserSecurityKey
update: updateAuthUserSecurityKeys
update_by_pk: updateAuthUserSecurityKey
object_relationships:
- name: user
using:
foreign_key_constraint_on: user_id

View File

@@ -0,0 +1,122 @@
table:
name: users
schema: auth
configuration:
column_config:
active_mfa_type:
custom_name: activeMfaType
avatar_url:
custom_name: avatarUrl
created_at:
custom_name: createdAt
default_role:
custom_name: defaultRole
disabled:
custom_name: disabled
display_name:
custom_name: displayName
email:
custom_name: email
email_verified:
custom_name: emailVerified
id:
custom_name: id
is_anonymous:
custom_name: isAnonymous
last_seen:
custom_name: lastSeen
locale:
custom_name: locale
new_email:
custom_name: newEmail
otp_hash:
custom_name: otpHash
otp_hash_expires_at:
custom_name: otpHashExpiresAt
otp_method_last_used:
custom_name: otpMethodLastUsed
password_hash:
custom_name: passwordHash
phone_number:
custom_name: phoneNumber
phone_number_verified:
custom_name: phoneNumberVerified
ticket:
custom_name: ticket
ticket_expires_at:
custom_name: ticketExpiresAt
totp_secret:
custom_name: totpSecret
updated_at:
custom_name: updatedAt
webauthn_current_challenge:
custom_name: currentChallenge
custom_column_names:
active_mfa_type: activeMfaType
avatar_url: avatarUrl
created_at: createdAt
default_role: defaultRole
disabled: disabled
display_name: displayName
email: email
email_verified: emailVerified
id: id
is_anonymous: isAnonymous
last_seen: lastSeen
locale: locale
new_email: newEmail
otp_hash: otpHash
otp_hash_expires_at: otpHashExpiresAt
otp_method_last_used: otpMethodLastUsed
password_hash: passwordHash
phone_number: phoneNumber
phone_number_verified: phoneNumberVerified
ticket: ticket
ticket_expires_at: ticketExpiresAt
totp_secret: totpSecret
updated_at: updatedAt
webauthn_current_challenge: currentChallenge
custom_name: users
custom_root_fields:
delete: deleteUsers
delete_by_pk: deleteUser
insert: insertUsers
insert_one: insertUser
select: users
select_aggregate: usersAggregate
select_by_pk: user
update: updateUsers
update_by_pk: updateUser
object_relationships:
- name: defaultRoleByRole
using:
foreign_key_constraint_on: default_role
array_relationships:
- name: refreshTokens
using:
foreign_key_constraint_on:
column: user_id
table:
name: refresh_tokens
schema: auth
- name: roles
using:
foreign_key_constraint_on:
column: user_id
table:
name: user_roles
schema: auth
- name: securityKeys
using:
foreign_key_constraint_on:
column: user_id
table:
name: user_security_keys
schema: auth
- name: userProviders
using:
foreign_key_constraint_on:
column: user_id
table:
name: user_providers
schema: auth

View File

@@ -0,0 +1,49 @@
table:
name: buckets
schema: storage
configuration:
column_config:
cache_control:
custom_name: cacheControl
created_at:
custom_name: createdAt
download_expiration:
custom_name: downloadExpiration
id:
custom_name: id
max_upload_file_size:
custom_name: maxUploadFileSize
min_upload_file_size:
custom_name: minUploadFileSize
presigned_urls_enabled:
custom_name: presignedUrlsEnabled
updated_at:
custom_name: updatedAt
custom_column_names:
cache_control: cacheControl
created_at: createdAt
download_expiration: downloadExpiration
id: id
max_upload_file_size: maxUploadFileSize
min_upload_file_size: minUploadFileSize
presigned_urls_enabled: presignedUrlsEnabled
updated_at: updatedAt
custom_name: buckets
custom_root_fields:
delete: deleteBuckets
delete_by_pk: deleteBucket
insert: insertBuckets
insert_one: insertBucket
select: buckets
select_aggregate: bucketsAggregate
select_by_pk: bucket
update: updateBuckets
update_by_pk: updateBucket
array_relationships:
- name: files
using:
foreign_key_constraint_on:
column: bucket_id
table:
name: files
schema: storage

View File

@@ -0,0 +1,51 @@
table:
name: files
schema: storage
configuration:
column_config:
bucket_id:
custom_name: bucketId
created_at:
custom_name: createdAt
etag:
custom_name: etag
id:
custom_name: id
is_uploaded:
custom_name: isUploaded
mime_type:
custom_name: mimeType
name:
custom_name: name
size:
custom_name: size
updated_at:
custom_name: updatedAt
uploaded_by_user_id:
custom_name: uploadedByUserId
custom_column_names:
bucket_id: bucketId
created_at: createdAt
etag: etag
id: id
is_uploaded: isUploaded
mime_type: mimeType
name: name
size: size
updated_at: updatedAt
uploaded_by_user_id: uploadedByUserId
custom_name: files
custom_root_fields:
delete: deleteFiles
delete_by_pk: deleteFile
insert: insertFiles
insert_one: insertFile
select: files
select_aggregate: filesAggregate
select_by_pk: file
update: updateFiles
update_by_pk: updateFile
object_relationships:
- name: bucket
using:
foreign_key_constraint_on: bucket_id

View File

@@ -0,0 +1,11 @@
- "!include auth_provider_requests.yaml"
- "!include auth_providers.yaml"
- "!include auth_refresh_token_types.yaml"
- "!include auth_refresh_tokens.yaml"
- "!include auth_roles.yaml"
- "!include auth_user_providers.yaml"
- "!include auth_user_roles.yaml"
- "!include auth_user_security_keys.yaml"
- "!include auth_users.yaml"
- "!include storage_buckets.yaml"
- "!include storage_files.yaml"

View File

@@ -0,0 +1 @@
disabled_for_roles: []

View File

@@ -0,0 +1 @@
[]

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1 @@
version: 3

View File

@@ -0,0 +1 @@
DELETE FROM "storage"."buckets" WHERE "id" = 'custom';

View File

@@ -0,0 +1 @@
INSERT INTO "storage"."buckets"("presigned_urls_enabled", "download_expiration", "max_upload_file_size", "min_upload_file_size", "cache_control", "id", "created_at", "updated_at") VALUES (true, 30, 30000000, 1, E'max-age=3600', E'custom', E'2023-06-29T14:30:13.859559+00:00', E'2023-06-29T14:30:13.859559+00:00');

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/node-storage",
"version": "0.0.2",
"version": "0.0.4",
"private": true,
"description": "This is an example of how to use the Storage with Node.js",
"main": "src/index.mjs",
@@ -14,9 +14,12 @@
"@nhost/nhost-js": "*",
"dotenv": "^16.1.3",
"form-data": "^4.0.0",
"node-fetch": "^3.3.0"
"fs-extra": "^11.1.1",
"node-fetch": "^3.3.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"@types/node": "^18.15.11"
"@types/node": "^18.15.11",
"@types/uuid": "^9.0.1"
}
}

View File

@@ -10,14 +10,23 @@ dependencies:
form-data:
specifier: ^4.0.0
version: 4.0.0
fs-extra:
specifier: ^11.1.1
version: 11.1.1
node-fetch:
specifier: ^3.3.0
version: 3.3.0
uuid:
specifier: ^9.0.0
version: 9.0.0
devDependencies:
'@types/node':
specifier: ^18.15.11
version: 18.15.11
'@types/uuid':
specifier: ^9.0.1
version: 9.0.1
packages:
@@ -32,6 +41,10 @@ packages:
resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==}
dev: true
/@types/uuid@9.0.1:
resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==}
dev: true
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: false
@@ -92,6 +105,27 @@ packages:
fetch-blob: 3.2.0
dev: false
/fs-extra@11.1.1:
resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==}
engines: {node: '>=14.14'}
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.1.0
universalify: 2.0.0
dev: false
/graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
dev: false
/jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
dependencies:
universalify: 2.0.0
optionalDependencies:
graceful-fs: 4.2.11
dev: false
/jwt-decode@3.1.2:
resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==}
dev: false
@@ -142,6 +176,16 @@ packages:
engines: {node: '>=4'}
dev: false
/universalify@2.0.0:
resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
engines: {node: '>= 10.0.0'}
dev: false
/uuid@9.0.0:
resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==}
hasBin: true
dev: false
/web-streams-polyfill@3.2.1:
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
engines: {node: '>= 8'}

View File

@@ -1,4 +1,7 @@
import { NhostClient } from '@nhost/nhost-js'
import dotenv from 'dotenv'
dotenv.config()
/**
* Create a new Nhost client.

View File

@@ -1,62 +1,17 @@
import dotenv from 'dotenv'
import FormData from 'form-data'
import fetch from 'node-fetch'
import { createClient } from './client.mjs'
import { uploadFile } from './uploadFile.mjs'
import { uploadFormData } from './uploadFormData.mjs'
import { uploadToBucket } from './uploadToBucket.mjs'
dotenv.config()
async function uploadFiles() {
await uploadFormData()
const client = createClient()
console.info('-----')
async function uploadImage() {
try {
// Download image from remote URL
const response = await fetch(
'https://hips.hearstapps.com/hmg-prod/images/cute-cat-photos-1593441022.jpg?crop=1.00xw:0.753xh;0,0.153xh&resize=1200:*'
)
await uploadFile()
if (!response.ok) {
console.error('Image not found!')
console.info('-----')
return
}
const arrayBuffer = await response.arrayBuffer()
const formData = new FormData()
formData.append('file[]', Buffer.from(arrayBuffer), 'cat.jpg')
// Upload file to Nhost Storage
const { error: uploadError, fileMetadata } = await client.storage.upload({
formData,
headers: { ...formData.getHeaders() }
})
if (uploadError) {
console.error(uploadError)
return
}
console.info(`File has been uploaded successfully!`)
const uploadedFile =
'processedFiles' in fileMetadata ? fileMetadata.processedFiles[0] : fileMetadata
console.info(`ID: ${uploadedFile?.id}`)
// Generate a presigned URL for the uploaded file
const { error: presignError, presignedUrl: blurredImage } =
await client.storage.getPresignedUrl({ fileId: uploadedFile.id })
if (presignError) {
console.error(presignError)
return
}
console.info(`Presigned URL: ${blurredImage.url}`)
} catch (error) {
console.error(error.message)
}
await uploadToBucket()
}
uploadImage()
uploadFiles()

View File

@@ -0,0 +1,70 @@
import fs from 'fs'
import fetch from 'node-fetch'
import { v4 as uuidv4 } from 'uuid'
import { createClient } from './client.mjs'
const client = createClient()
export async function uploadFile() {
console.info('Uploading a Single File Directly...')
try {
// Download image from remote URL
const response = await fetch(
'https://hips.hearstapps.com/hmg-prod/images/cute-cat-photos-1593441022.jpg?crop=1.00xw:0.753xh;0,0.153xh&resize=1200:*'
)
if (!response.ok) {
console.error(`[file]`, 'Image not found!')
return
}
const arrayBuffer = await response.arrayBuffer()
const fileBuffer = Buffer.from(arrayBuffer)
const fileName = 'cat.jpg'
fs.writeFile(fileName, fileBuffer, async (err) => {
if (err) {
console.error(`[file]`, err)
return
}
const file = fs.createReadStream(fileName)
const customFileId = uuidv4()
const { error: uploadError, fileMetadata } = await client.storage.upload({
file,
id: customFileId
})
if (uploadError) {
console.error(`[file]`, uploadError)
return
}
console.info(`[file]`, `File has been uploaded successfully!`)
console.info(`[file]`, `ID: ${fileMetadata?.id}`)
console.info(`[file]`, `Matches custom ID: ${fileMetadata?.id === customFileId}`)
// Generate a presigned URL for the uploaded file
const { error: presignError, presignedUrl: image } = await client.storage.getPresignedUrl({
fileId: fileMetadata.id
})
if (presignError) {
console.error(`[file]`, presignError)
return
}
console.info(`[file]`, `Presigned URL: ${image.url}`)
})
// Upload file to Nhost Storage
} catch (error) {
console.error(`[file]`, error.message)
}
}

View File

@@ -0,0 +1,90 @@
import FormData from 'form-data'
import fetch from 'node-fetch'
import { v4 as uuidv4 } from 'uuid'
import { createClient } from './client.mjs'
const client = createClient()
export async function uploadFormData() {
console.info('Uploading 2 Files via Form Data...')
try {
// Download image from remote URL
const response = await fetch(
'https://hips.hearstapps.com/hmg-prod/images/cute-cat-photos-1593441022.jpg?crop=1.00xw:0.753xh;0,0.153xh&resize=1200:*'
)
if (!response.ok) {
console.error(`[form-data]`, 'Image not found!')
return
}
const arrayBuffer = await response.arrayBuffer()
const customValues = [
{
id: uuidv4(),
name: 'cat1.jpg'
},
{
id: uuidv4(),
name: 'cat2.jpg'
}
]
const formData = new FormData()
formData.append('file[]', Buffer.from(arrayBuffer), customValues[0].name)
formData.append('metadata[]', JSON.stringify({ id: customValues[0].id }))
formData.append('file[]', Buffer.from(arrayBuffer), customValues[1].name)
formData.append('metadata[]', JSON.stringify({ id: customValues[1].id }))
// Upload files to Nhost Storage
const { error: uploadError, fileMetadata } = await client.storage.upload({
formData,
headers: { ...formData.getHeaders() }
})
if (uploadError) {
console.error(`[form-data]`, uploadError)
return
}
if (fileMetadata.processedFiles.length !== 2) {
console.error(
`[form-data]`,
`Expected 2 files to be uploaded, but got ${fileMetadata.processedFiles.length}`
)
return
}
await Promise.all(
fileMetadata?.processedFiles.map(async (processedFile, index) => {
console.info(`[form-data]`, `File has been uploaded successfully!`)
console.info(`[form-data]`, `ID: ${processedFile.id}`)
console.info(
`[form-data]`,
`Matches custom ID: ${customValues.some(({ id }) => processedFile.id === id)}`
)
// Generate a presigned URL for the uploaded file
const { error: presignError, presignedUrl: image } = await client.storage.getPresignedUrl({
fileId: processedFile.id
})
if (presignError) {
console.error(`[form-data]`, presignError)
return
}
console.info(`[form-data]`, `Presigned URL: ${image.url}`)
})
)
} catch (error) {
console.error(`[form-data]`, error.message)
}
}

View File

@@ -0,0 +1,68 @@
import fs from 'fs'
import fetch from 'node-fetch'
import { createClient } from './client.mjs'
const client = createClient()
export async function uploadToBucket() {
console.info('Uploading a Single File to a custom bucket...')
try {
// Download image from remote URL
const response = await fetch(
'https://hips.hearstapps.com/hmg-prod/images/cute-cat-photos-1593441022.jpg?crop=1.00xw:0.753xh;0,0.153xh&resize=1200:*'
)
if (!response.ok) {
console.error(`[file-to-bucket]`, 'Image not found!')
return
}
const arrayBuffer = await response.arrayBuffer()
const fileBuffer = Buffer.from(arrayBuffer)
const fileName = 'cat.jpg'
fs.writeFile(fileName, fileBuffer, async (err) => {
if (err) {
console.error(`[file-to-bucket]`, err)
return
}
const file = fs.createReadStream(fileName)
const { error: uploadError, fileMetadata } = await client.storage.upload({
file,
bucketId: 'custom'
})
if (uploadError) {
console.error(`[file-to-bucket]`, uploadError)
return
}
console.info(`[file-to-bucket]`, `File has been uploaded successfully!`)
console.info(`[file-to-bucket]`, `ID: ${fileMetadata?.id}`)
console.log(fileMetadata.bucketId)
// Generate a presigned URL for the uploaded file
const { error: presignError, presignedUrl: image } = await client.storage.getPresignedUrl({
fileId: fileMetadata.id
})
if (presignError) {
console.error(`[file-to-bucket]`, presignError)
return
}
console.info(`[file-to-bucket]`, `Presigned URL: ${image.url}`)
})
// Upload file to Nhost Storage
} catch (error) {
console.error(`[file-to-bucket]`, error.message)
}
}

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"allowJs": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true,
"strictNullChecks": false
}
}

View File

@@ -1,5 +1,14 @@
# @nhost-examples/react-apollo
## 0.1.14
### Patch Changes
- b3b64a3b7: chore(deps): bump `@types/react` to `v18.2.14` and `@types/react-dom` to `v18.2.6`
- Updated dependencies [b3b64a3b7]
- @nhost/react-apollo@5.0.29
- @nhost/react@2.0.25
## 0.1.13
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/react-apollo",
"version": "0.1.13",
"version": "0.1.14",
"private": true,
"dependencies": {
"@apollo/client": "^3.7.14",
@@ -54,8 +54,8 @@
"@nuintun/qrcode": "^3.3.2",
"@playwright/test": "1.31.0",
"@types/pngjs": "^6.0.1",
"@types/react": "^18.2.6",
"@types/react-dom": "^18.2.4",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@types/totp-generator": "^0.0.4",
"@vitejs/plugin-react": "^3.1.0",
"@xstate/inspect": "^0.6.5",

View File

@@ -1,5 +1,13 @@
# @nhost-examples/react-gqty
## 0.0.9
### Patch Changes
- b3b64a3b7: chore(deps): bump `@types/react` to `v18.2.14` and `@types/react-dom` to `v18.2.6`
- Updated dependencies [b3b64a3b7]
- @nhost/react@2.0.25
## 0.0.8
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/react-gqty",
"private": true,
"version": "0.0.8",
"version": "0.0.9",
"type": "module",
"scripts": {
"dev": "vite",
@@ -20,8 +20,8 @@
"devDependencies": {
"@gqty/cli": "3.3.0-alpha-d8cdbf6.0",
"@tailwindcss/forms": "^0.5.3",
"@types/react": "^18.0.34",
"@types/react-dom": "^18.0.11",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@vitejs/plugin-react": "^3.0.0",
"autoprefixer": "^10.4.12",
"postcss": "^8.4.18",

View File

@@ -1,5 +1,11 @@
# @nhost-examples/seed-data-storage
## 0.0.4
### Patch Changes
- a5305e6b5: docs: update old URLs to the new format
## 0.0.3
### Patch Changes

View File

@@ -22,8 +22,8 @@ The database and storage have now been seeded successfully.
You can now try to fetch the seeded files:
- [http://localhost:1337/v1/storage/files/3d62252d-8db2-4b2b-ba63-f2ef64af4267](http://localhost:1337/v1/storage/files/3d62252d-8db2-4b2b-ba63-f2ef64af4267)
- [http://localhost:1337/v1/storage/files/039f89ef-f151-418f-b2db-83c94fbf0fa5](http://localhost:1337/v1/storage/files/039f89ef-f151-418f-b2db-83c94fbf0fa5)
- [https://local.storage.nhost.run/v1/files/3d62252d-8db2-4b2b-ba63-f2ef64af4267](https://local.storage.nhost.run/v1/files/3d62252d-8db2-4b2b-ba63-f2ef64af4267)
- [https://local.storage.nhost.run/v1/files/039f89ef-f151-418f-b2db-83c94fbf0fa5](https://local.storage.nhost.run/v1/files/039f89ef-f151-418f-b2db-83c94fbf0fa5)
And make a GraphQL request:

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/seed-data-storage",
"private": true,
"version": "0.0.3",
"version": "0.0.4",
"scripts": {
"seed-storage": "./seed-storage.sh"
}

View File

@@ -10,7 +10,7 @@ jq -c '.[]' input.json | while read i; do
-H "Content-Type: multipart/form-data" \
-H "x-hasura-admin-secret: nhost-admin-secret" \
-F "file=@$path" \
http://localhost:1337/v1/storage/files/$id
https://local.storage.nhost.run/v1/files/$id
done

View File

@@ -125,7 +125,7 @@
buildInputs = with pkgs; [
nhost
nodejs_18
nodePackages.pnpm
# nodePackages.pnpm
] ++ buildInputs ++ nativeBuildInputs;
};
};

View File

@@ -1,5 +1,17 @@
# @nhost/apollo
## 5.2.14
### Patch Changes
- @nhost/nhost-js@2.2.12
## 5.2.13
### Patch Changes
- @nhost/nhost-js@2.2.11
## 5.2.12
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/apollo",
"version": "5.2.12",
"version": "5.2.14",
"description": "Nhost Apollo Client library",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,11 @@
# @nhost/google-translation
## 0.0.6
### Patch Changes
- a5305e6b5: docs: update old URLs to the new format
## 0.0.5
### Patch Changes

View File

@@ -110,7 +110,7 @@ Learn more about the [Nhost CLI](https://docs.nhost.io/platform/cli).
Test the Google Translation GraphQL API in the browser:
[http://localhost:1337/v1/functions/graphql/google-translation](http://localhost:1337/v1/functions/graphql/google-translation)
[https://local.functions.nhost.run/v1/graphql/google-translation](https://local.functions.nhost.run/v1/graphql/google-translation)
### Remote Schema
@@ -119,7 +119,7 @@ Add the Google Translation GraphQL API as a Remote Schema in Hasura.
**URL**
```
{{NHOST_BACKEND_URL}}/v1/functions/graphql/google-translation
https://local.functions.nhost.run/v1/graphql/google-translation
```
**Headers**

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/google-translation",
"version": "0.0.5",
"version": "0.0.6",
"description": "Google Translation GraphQL API",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,27 @@
# @nhost/react-apollo
## 5.0.31
### Patch Changes
- @nhost/apollo@5.2.14
- @nhost/react@2.0.27
## 5.0.30
### Patch Changes
- @nhost/apollo@5.2.13
- @nhost/react@2.0.26
## 5.0.29
### Patch Changes
- b3b64a3b7: chore(deps): bump `@types/react` to `v18.2.14` and `@types/react-dom` to `v18.2.6`
- Updated dependencies [b3b64a3b7]
- @nhost/react@2.0.25
## 5.0.28
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react-apollo",
"version": "5.0.28",
"version": "5.0.31",
"description": "Nhost React Apollo client",
"license": "MIT",
"keywords": [
@@ -73,7 +73,7 @@
"devDependencies": {
"@apollo/client": "^3.7.10",
"@nhost/react": "workspace:*",
"@types/react": "^18.0.34",
"@types/react": "^18.2.14",
"graphql": "16.7.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"

View File

@@ -1,5 +1,25 @@
# @nhost/react-urql
## 2.0.28
### Patch Changes
- @nhost/react@2.0.27
## 2.0.27
### Patch Changes
- @nhost/react@2.0.26
## 2.0.26
### Patch Changes
- b3b64a3b7: chore(deps): bump `@types/react` to `v18.2.14` and `@types/react-dom` to `v18.2.6`
- Updated dependencies [b3b64a3b7]
- @nhost/react@2.0.25
## 2.0.25
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react-urql",
"version": "2.0.25",
"version": "2.0.28",
"description": "Nhost React URQL client",
"license": "MIT",
"keywords": [
@@ -72,7 +72,7 @@
},
"devDependencies": {
"@nhost/react": "workspace:*",
"@types/react": "^18.0.34",
"@types/react": "^18.2.14",
"graphql": "16.7.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@@ -629,6 +629,103 @@
"title": "Network Traffic",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "This graph shows when a service was restarted. There are two main reasons why a service may be restarted:\n\n- OOMKilled - This means the service tried to use more memory than it has available and had to be restarted. For more information on resources you can check the [documentation](https://docs.nhost.io/platform/compute).\n- Error - This can show for mainly two reasons; when new configuration needs to be applied the service is terminated and due to limitations this shows as \"Error\" but it is, in fact, part of normal operations. This can also show if your service is misconfigured and/or can't start correctly for some reason. If this error doesn't show constantly it is safe to ignore this error.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 2,
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 29
},
"id": 37,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum by(container, reason) (increase(pod_terminated_total[$__rate_interval]))",
"hide": false,
"interval": "2m",
"legendFormat": "{{container}}-{{reason}}",
"range": true,
"refId": "A"
}
],
"title": "Service Restarts",
"type": "timeseries"
},
{
"collapsed": false,
"gridPos": {

View File

@@ -76,7 +76,7 @@
"husky": "^8.0.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"turbo": "1.10.5",
"turbo": "1.10.6",
"typedoc": "^0.22.18",
"typescript": "4.9.5",
"vite": "^4.3.8",

View File

@@ -1,5 +1,17 @@
# @nhost/hasura-storage-js
## 2.2.1
### Patch Changes
- d54e4cdd4: fix(buckets): allow using custom buckets for upload
## 2.2.0
### Minor Changes
- 2cdb13b3e: fix(upload): allow specifying `id` and `name` only when not using `form-data`
## 2.1.6
### Patch Changes

View File

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

View File

@@ -0,0 +1,26 @@
table:
name: refresh_token_types
schema: auth
is_enum: true
configuration:
column_config: {}
custom_column_names: {}
custom_name: authRefreshTokenTypes
custom_root_fields:
delete: deleteAuthRefreshTokenTypes
delete_by_pk: deleteAuthRefreshTokenType
insert: insertAuthRefreshTokenTypes
insert_one: insertAuthRefreshTokenType
select: authRefreshTokenTypes
select_aggregate: authRefreshTokenTypesAggregate
select_by_pk: authRefreshTokenType
update: updateAuthRefreshTokenTypes
update_by_pk: updateAuthRefreshTokenType
array_relationships:
- name: refreshTokens
using:
foreign_key_constraint_on:
column: type
table:
name: refresh_tokens
schema: auth

View File

@@ -7,14 +7,14 @@ configuration:
custom_name: createdAt
expires_at:
custom_name: expiresAt
refresh_token:
custom_name: refreshToken
refresh_token_hash:
custom_name: refreshTokenHash
user_id:
custom_name: userId
custom_column_names:
created_at: createdAt
expires_at: expiresAt
refresh_token: refreshToken
refresh_token_hash: refreshTokenHash
user_id: userId
custom_name: authRefreshTokens
custom_root_fields:
@@ -31,3 +31,25 @@ object_relationships:
- name: user
using:
foreign_key_constraint_on: user_id
select_permissions:
- role: user
permission:
columns:
- id
- created_at
- expires_at
- metadata
- type
- user_id
filter:
user_id:
_eq: X-Hasura-User-Id
delete_permissions:
- role: user
permission:
filter:
_and:
- user_id:
_eq: X-Hasura-User-Id
- type:
_eq: pat

View File

@@ -1,5 +1,6 @@
- "!include auth_provider_requests.yaml"
- "!include auth_providers.yaml"
- "!include auth_refresh_token_types.yaml"
- "!include auth_refresh_tokens.yaml"
- "!include auth_roles.yaml"
- "!include auth_user_providers.yaml"

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/hasura-storage-js",
"version": "2.1.6",
"version": "2.2.1",
"description": "Hasura-storage client",
"license": "MIT",
"keywords": [

View File

@@ -1,11 +1,14 @@
import fetchPonyfill from 'fetch-ponyfill'
import FormData from 'form-data'
import {
ApiDeleteParams,
ApiDeleteResponse,
ApiGetPresignedUrlParams,
ApiGetPresignedUrlResponse,
StorageUploadFileParams,
StorageUploadFileResponse,
StorageUploadFormDataParams,
StorageUploadResponse
StorageUploadFormDataResponse
} from './utils/types'
import { fetchUpload } from './utils/upload'
@@ -24,21 +27,65 @@ export class HasuraStorageApi {
this.url = url
}
async upload({
async uploadFormData({
formData,
headers,
bucketId
}: StorageUploadFormDataParams): Promise<StorageUploadFormDataResponse> {
const { error, fileMetadata } = await fetchUpload(this.url, formData, {
accessToken: this.accessToken,
adminSecret: this.adminSecret,
bucketId,
headers
})
if (error) {
return { fileMetadata: null, error }
}
if (fileMetadata && !('processedFiles' in fileMetadata)) {
return {
fileMetadata: {
processedFiles: [fileMetadata]
},
error: null
}
}
return { fileMetadata, error: null }
}
async uploadFile({
file,
bucketId,
id,
name
}: StorageUploadFormDataParams): Promise<StorageUploadResponse> {
return fetchUpload(this.url, formData, {
}: StorageUploadFileParams): Promise<StorageUploadFileResponse> {
const formData = new FormData()
formData.append('file[]', file)
formData.append('metadata[]', JSON.stringify({ id, name }))
const { error, fileMetadata } = await fetchUpload(this.url, formData, {
accessToken: this.accessToken,
adminSecret: this.adminSecret,
bucketId,
fileId: id,
name,
headers
name
})
if (error) {
return { fileMetadata: null, error }
}
if (fileMetadata && 'processedFiles' in fileMetadata) {
return {
fileMetadata: fileMetadata.processedFiles[0],
error: null
}
}
return { fileMetadata, error: null }
}
async getPresignedUrl(params: ApiGetPresignedUrlParams): Promise<ApiGetPresignedUrlResponse> {

View File

@@ -1,4 +1,3 @@
import FormData from 'form-data'
import { HasuraStorageApi } from './hasura-storage-api'
import {
appendImageTransformationParameters,
@@ -8,7 +7,9 @@ import {
StorageGetPresignedUrlResponse,
StorageGetUrlParams,
StorageUploadFileParams,
StorageUploadFileResponse,
StorageUploadFormDataParams,
StorageUploadFormDataResponse,
StorageUploadParams,
StorageUploadResponse
} from './utils'
@@ -73,21 +74,14 @@ export class HasuraStorageClient {
* @docs https://docs.nhost.io/reference/javascript/storage/upload
*/
async upload(params: StorageUploadFileParams): Promise<StorageUploadResponse>
async upload(params: StorageUploadFormDataParams): Promise<StorageUploadResponse>
async upload(params: StorageUploadFileParams): Promise<StorageUploadFileResponse>
async upload(params: StorageUploadFormDataParams): Promise<StorageUploadFormDataResponse>
async upload(params: StorageUploadParams): Promise<StorageUploadResponse> {
if ('file' in params) {
const formData = new FormData()
formData.append('file[]', params.file)
return this.api.upload({
...params,
formData
})
return this.api.uploadFile(params)
}
return this.api.upload(params)
return this.api.uploadFormData(params)
}
/**

View File

@@ -36,8 +36,6 @@ export interface StorageUploadFileParams {
// works in browser and server
export interface StorageUploadFormDataParams {
formData: FormData
id?: string
name?: string
bucketId?: string
headers?: Record<string, string>
}
@@ -45,10 +43,16 @@ export interface StorageUploadFormDataParams {
// works in browser and server
export type StorageUploadParams = StorageUploadFileParams | StorageUploadFormDataParams
export type StorageUploadResponse =
| { fileMetadata: FileResponse | { processedFiles: FileResponse[] }; error: null }
export type StorageUploadFileResponse =
| { fileMetadata: FileResponse; error: null }
| { fileMetadata: null; error: StorageErrorPayload }
export type StorageUploadFormDataResponse =
| { fileMetadata: { processedFiles: FileResponse[] }; error: null }
| { fileMetadata: null; error: StorageErrorPayload }
export type StorageUploadResponse = StorageUploadFileResponse | StorageUploadFormDataResponse
export interface StorageImageTransformationParams {
/** Image width, in pixels */
width?: number

View File

@@ -38,15 +38,8 @@ export const fetchUpload = async (
const headers: HeadersInit = {
...initialHeaders
}
if (fileId) {
headers['x-nhost-file-id'] = fileId
}
if (bucketId) {
headers['x-nhost-bucket-id'] = bucketId
}
if (name) {
headers['x-nhost-file-name'] = toIso88591(name)
data.append('bucket-id', bucketId)
}
if (adminSecret) {
headers['x-hasura-admin-secret'] = adminSecret

View File

@@ -1,17 +1,15 @@
import fs from 'fs'
import { describe, expect, it } from 'vitest'
import { v4 as uuidv4 } from 'uuid'
import { describe, expect, it } from 'vitest'
import { storage } from './utils/helpers'
import FormData from 'form-data'
describe('test delete file', () => {
it('should be able to get delete file', async () => {
const fd = new FormData()
fd.append('file', fs.createReadStream('./tests/assets/sample.pdf'))
const file = fs.createReadStream('./tests/assets/sample.pdf')
const { fileMetadata } = await storage.upload({
formData: fd
file: file as unknown as File
})
const { error } = await storage.delete({

View File

@@ -38,11 +38,10 @@ describe('test upload', () => {
it('should upload a file with specific id', async () => {
const RANDOM_UUID = uuidv4()
const fd = new FormData()
fd.append('file', fs.createReadStream('./tests/assets/sample.pdf'))
const file = fs.createReadStream('./tests/assets/sample.pdf')
const { fileMetadata, error } = await storage.upload({
formData: fd,
file: file as unknown as File,
id: RANDOM_UUID
})
@@ -50,8 +49,7 @@ describe('test upload', () => {
throw new Error('fileMetadata is missing')
}
const { id: fileId } =
'processedFiles' in fileMetadata ? fileMetadata.processedFiles[0] : fileMetadata
const { id: fileId } = fileMetadata
expect(error).toBeNull()
expect(fileId).toBe(RANDOM_UUID)
@@ -60,11 +58,10 @@ describe('test upload', () => {
it('should upload a file with specific name', async () => {
const FILE_NAME = 'special-name.pdf'
const fd = new FormData()
fd.append('file', fs.createReadStream('./tests/assets/sample.pdf'))
const file = fs.createReadStream('./tests/assets/sample.pdf')
const { fileMetadata, error } = await storage.upload({
formData: fd,
file: file as unknown as File,
name: FILE_NAME
})
@@ -72,19 +69,17 @@ describe('test upload', () => {
throw new Error('fileMetadata is missing')
}
const { name: fileName } =
'processedFiles' in fileMetadata ? fileMetadata.processedFiles[0] : fileMetadata
const { name: fileName } = fileMetadata
expect(error).toBeNull()
expect(fileName).toBe(FILE_NAME)
})
it('should upload a file with a non-ISO 8859-1 name', async () => {
const fd = new FormData()
fd.append('file', fs.createReadStream('./tests/assets/sample.pdf'))
const file = fs.createReadStream('./tests/assets/sample.pdf')
const { fileMetadata, error } = await storage.upload({
formData: fd,
file: file as unknown as File,
name: '你 好'
})
@@ -92,8 +87,7 @@ describe('test upload', () => {
throw new Error('fileMetadata is missing')
}
const { name: fileName } =
'processedFiles' in fileMetadata ? fileMetadata.processedFiles[0] : fileMetadata
const { name: fileName } = fileMetadata
expect(error).toBeNull()
expect(fileName).toMatchInlineSnapshot('"%E4%BD%A0%20%E5%A5%BD"')
@@ -103,11 +97,10 @@ describe('test upload', () => {
const RANDOM_UUID = uuidv4()
const FILE_NAME = 'special-name.pdf'
const fd = new FormData()
fd.append('file', fs.createReadStream('./tests/assets/sample.pdf'))
const file = fs.createReadStream('./tests/assets/sample.pdf')
const { fileMetadata, error } = await storage.upload({
formData: fd,
file: file as unknown as File,
id: RANDOM_UUID,
name: FILE_NAME
})
@@ -116,8 +109,7 @@ describe('test upload', () => {
throw new Error('fileMetadata is missing')
}
const { id: fileId, name: fileName } =
'processedFiles' in fileMetadata ? fileMetadata.processedFiles[0] : fileMetadata
const { id: fileId, name: fileName } = fileMetadata
expect(error).toBeNull()
expect(fileId).toBe(RANDOM_UUID)

View File

@@ -1,5 +1,24 @@
# @nhost/nextjs
## 1.13.33
### Patch Changes
- @nhost/react@2.0.27
## 1.13.32
### Patch Changes
- @nhost/react@2.0.26
## 1.13.31
### Patch Changes
- Updated dependencies [b3b64a3b7]
- @nhost/react@2.0.25
## 1.13.30
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/nextjs",
"version": "1.13.30",
"version": "1.13.33",
"description": "Nhost NextJS library",
"license": "MIT",
"keywords": [

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