Compare commits

..

7 Commits

312 changed files with 4865 additions and 53264 deletions

View File

@@ -24,4 +24,5 @@ jobs:
config.model: ${{ vars.GEN_AI_MODEL }}
config.model_turbo: $${{ vars.GEN_AI_MODEL_TURBO }}
config.max_model_tokens: 200000
config.custom_model_max_tokens: 200000
ignore.glob: "['pnpm-lock.yaml','**/pnpm-lock.yaml', 'vendor/**','**/client_gen.go','**/models_gen.go','**/generated.go','**/*.gen.go']"

View File

@@ -2,5 +2,7 @@
// $schema provides code completion hints to IDEs.
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
"moderate": true,
"allowlist": ["vue-template-compiler", { "id": "CVE-2025-48068", "path": "next" }]
"allowlist": [
"GHSA-9965-vmph-33xx" // https://github.com/advisories/GHSA-9965-vmph-33xx Update package once have a fix
]
}

View File

@@ -44,7 +44,7 @@ if [[ "$version" == "latest" ]]; then
release=$(curl --silent https://api.github.com/repos/nhost/nhost/releases\?per_page=100 | grep tag_name | grep \"cli\@ | head -n 1 | sed 's/.*"tag_name": "\([^"]*\)".*/\1/')
version=$( echo $release | sed 's/.*@//')
else
release="cli@$release"
release="cli@$version"
fi
# check version exists

View File

@@ -1,47 +0,0 @@
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'storybook-addon-next-router',
{
/**
* Fix Storybook issue with PostCSS@8
* @see https://github.com/storybookjs/storybook/issues/12668#issuecomment-773958085
*/
name: '@storybook/addon-postcss',
options: {
postcssLoaderOptions: {
implementation: require('postcss'),
},
},
},
],
framework: '@storybook/react',
core: {
builder: '@storybook/builder-webpack5',
},
features: {
emotionAlias: true,
},
webpackFinal: async (config) => {
return {
...config,
resolve: {
...config?.resolve,
plugins: [
...(config?.resolve?.plugins || []),
new TsconfigPathsPlugin(),
],
},
};
},
env: (config) => ({
...config,
NEXT_PUBLIC_ENV: 'dev',
NEXT_PUBLIC_NHOST_PLATFORM: 'false',
}),
};

View File

@@ -1,69 +0,0 @@
import { NhostProvider } from '@/providers/nhost';
import '@fontsource/inter';
import '@fontsource/inter/500.css';
import '@fontsource/inter/700.css';
import { CssBaseline, ThemeProvider } from '@mui/material';
import { createClient } from '@nhost/nhost-js-beta';
import { NhostApolloProvider } from '@nhost/react-apollo';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Buffer } from 'buffer';
import { initialize, mswDecorator } from 'msw-storybook-addon';
import { RouterContext } from 'next/dist/shared/lib/router-context';
import { createTheme } from '../src/components/ui/v2/createTheme';
import '../src/styles/globals.css';
global.Buffer = Buffer;
initialize({ onUnhandledRequest: 'bypass' });
const queryClient = new QueryClient();
export const parameters = {
nextRouter: {
Provider: RouterContext.Provider,
isReady: true,
},
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
export const decorators = [
(Story, context) => {
const isDarkMode = !context.globals?.backgrounds?.value
?.toLowerCase()
?.startsWith('#f');
return (
<ThemeProvider theme={createTheme(isDarkMode ? 'dark' : 'light')}>
<CssBaseline />
<Story />
</ThemeProvider>
);
},
(Story) => (
<QueryClientProvider client={queryClient}>
<Story />
</QueryClientProvider>
),
(Story) => (
<NhostApolloProvider
fetchPolicy="cache-first"
graphqlUrl="https://local.graphql.nhost.run/v1"
>
<Story />
</NhostApolloProvider>
),
(Story) => (
<NhostProvider
nhost={createClient({ subdomain: 'local', region: 'local' })}
>
<Story />
</NhostProvider>
),
mswDecorator,
];

View File

@@ -62,20 +62,6 @@ NEXT_PUBLIC_NHOST_HASURA_API_URL=https://local.hasura.local.nhost.run
This will connect the Nhost Dashboard to your locally running Nhost backend.
### Storybook
Components are documented using [Storybook](https://storybook.js.org/). To run Storybook, run the following command:
```bash
pnpm storybook
```
By default, Storybook will run on port `6006`. You can change this by passing the `--port` flag:
```bash
pnpm storybook --port 6007
```
### General Environment Variables
| Name | Description |

View File

@@ -9,15 +9,14 @@
"analyze": "ANALYZE=true pnpm build --no-lint",
"start": "next start",
"lint": "next lint --max-warnings 0",
"test": "vitest --run",
"test": "pnpm lint && pnpm test:vitest",
"test:vitest": "vitest --run",
"test:watch": "vitest",
"generate": "echo 'This needs to be fixed.'",
"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",
"codegen-hasura-api": "orval --config src/utils/hasura-api/orval.config.ts",
"format": "prettier --write \"src/**/*.{js,ts,tsx,jsx,json,md}\" --plugin-search-dir=.",
"storybook": "start-storybook -p 6006 -s public",
"build-storybook": "build-storybook",
"install-browsers": "pnpm playwright install && pnpm playwright install-deps",
"e2e:tests": "pnpm playwright test --config=playwright.config.ts -x",
"e2e": "pnpm e2e:tests --project=main",
@@ -111,7 +110,6 @@
"react-hot-toast": "^2.4.1",
"react-intersection-observer": "^9.8.1",
"react-is": "18.2.0",
"react-loading-skeleton": "^2.2.0",
"react-markdown": "^9.0.1",
"react-merge-refs": "^3.0.2",
"react-resizable-layout": "^0.7.2",
@@ -136,21 +134,12 @@
"@babel/core": "^7.24.3",
"@eslint/js": "9.26.0",
"@faker-js/faker": "^7.6.0",
"@graphql-codegen/cli": "^5.0.2",
"@graphql-codegen/cli": "^6.0.0",
"@graphql-codegen/typescript": "^3.0.4",
"@graphql-codegen/typescript-operations": "^3.0.4",
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
"@next/bundle-analyzer": "^12.3.4",
"@playwright/test": "1.54.1",
"@storybook/addon-actions": "^6.5.16",
"@storybook/addon-essentials": "^6.5.16",
"@storybook/addon-interactions": "^6.5.16",
"@storybook/addon-links": "^6.5.16",
"@storybook/addon-postcss": "^2.0.0",
"@storybook/builder-webpack5": "^6.5.16",
"@storybook/manager-webpack5": "^6.5.16",
"@storybook/react": "^7.6.17",
"@storybook/testing-library": "^0.2.2",
"@tailwindcss/typography": "^0.5.12",
"@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "^5.17.0",
@@ -160,7 +149,7 @@
"@types/bcryptjs": "^2.4.6",
"@types/jest": "^29.5.12",
"@types/lodash.debounce": "^4.0.9",
"@types/node": "^16.18.93",
"@types/node": "^20.14.8",
"@types/pluralize": "^0.0.30",
"@types/react": "18.2.73",
"@types/react-dom": "^18.2.23",
@@ -170,8 +159,8 @@
"@types/validator": "^13.11.9",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/coverage-v8": "^0.32.4",
"@vitejs/plugin-react": "^4.7.0",
"@vitest/coverage-v8": "^3.2.4",
"audit-ci": "^6.6.1",
"autoprefixer": "^10.4.19",
"babel-loader": "^8.3.0",
@@ -195,24 +184,21 @@
"eslint-plugin-vue": "^9.26.0",
"jsdom": "^22.1.0",
"lint-staged": "^15.2.2",
"msw": "^1.3.5",
"msw-storybook-addon": "^1.10.0",
"msw": "^2.11.4",
"node-fetch": "^3.3.2",
"orval": "^7.11.2",
"postcss": "^8.4.38",
"prettier": "^3.4.2",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-tailwindcss": "^0.6.11",
"react-date-fns-hooks": "^0.9.4",
"require-from-string": "^2.0.2",
"snake-case": "^3.0.4",
"storybook-addon-next-router": "^4.0.2",
"tailwindcss": "^3.4.12",
"ts-node": "^10.9.2",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"vite": "^5.4.20",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^0.32.4"
"vitest": "^3.2.4"
},
"browserslist": {
"production": [
@@ -243,6 +229,9 @@
"@lezer/highlight": "^1.0.0"
}
}
},
"overrides": {
"esbuild@<=0.24.2": ">=0.25.0"
}
}
}

10004
dashboard/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -66,7 +66,6 @@ let
"${submodule}/tsconfig.test.json"
"${submodule}/vitest.config.ts"
"${submodule}/vitest.global-setup.ts"
(inDirectory "${submodule}/.storybook")
(inDirectory "${submodule}/e2e")
(inDirectory "${submodule}/public")
(inDirectory "${submodule}/src")
@@ -213,5 +212,3 @@ rec {
}).copyTo}/bin/copy-to dir:$out
'';
}

View File

@@ -1,48 +0,0 @@
import type { ComponentMeta, ComponentStory } from '@storybook/react';
import type { PropsWithoutRef } from 'react';
import type { ReadOnlyToggleProps } from './ReadOnlyToggle';
import ReadOnlyToggle from './ReadOnlyToggle';
export default {
title: 'Common Components / ReadOnlyToggle',
component: ReadOnlyToggle,
argTypes: {
checked: {
options: [null, true, false],
control: { type: 'radio' },
},
},
} as ComponentMeta<typeof ReadOnlyToggle>;
const Template: ComponentStory<typeof ReadOnlyToggle> = function Template(
args: PropsWithoutRef<ReadOnlyToggleProps>,
) {
return <ReadOnlyToggle {...args} />;
};
export const Null = Template.bind({});
Null.args = {
checked: null,
};
export const True = Template.bind({});
True.args = {
checked: true,
};
export const False = Template.bind({});
False.args = {
checked: false,
};
export const CustomClasses = Template.bind({});
CustomClasses.args = {
checked: true,
className: '!bg-red-500',
slotProps: {
label: {
className: '!text-sm !text-white',
},
},
};

View File

@@ -1,125 +0,0 @@
import { PlusCircleIcon } from '@/components/ui/v2/icons/PlusCircleIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import type { Meta, StoryFn } from '@storybook/react';
import type { ButtonProps } from './Button';
import Button from './Button';
export default {
title: 'UI Library / Button',
component: Button,
argTypes: {
variant: {
options: ['contained', 'outlined', 'borderless'],
control: { type: 'radio' },
},
color: {
options: ['primary', 'secondary', 'error'],
control: { type: 'radio' },
},
disabled: {
control: { type: 'boolean' },
},
size: {
options: ['small', 'medium', 'large'],
control: { type: 'radio' },
},
},
} as Meta<typeof Button>;
const Template: StoryFn<ButtonProps> = function TemplateFunction(
args: ButtonProps,
) {
return <Button {...args} />;
};
export const Primary = Template.bind({});
Primary.args = {
children: 'Button',
color: 'primary',
};
export const PrimaryOutlined = Template.bind({});
PrimaryOutlined.args = {
children: 'Button',
variant: 'outlined',
color: 'primary',
};
export const PrimaryBorderless = Template.bind({});
PrimaryBorderless.args = {
children: 'Button',
variant: 'borderless',
color: 'primary',
};
export const Secondary = Template.bind({});
Secondary.args = {
children: 'Button',
color: 'secondary',
};
export const SecondaryOutlined = Template.bind({});
SecondaryOutlined.args = {
children: 'Button',
variant: 'outlined',
color: 'secondary',
};
export const SecondaryBorderless = Template.bind({});
SecondaryBorderless.args = {
children: 'Button',
variant: 'borderless',
color: 'secondary',
};
export const Danger = Template.bind({});
Danger.args = {
children: 'Button',
color: 'error',
};
export const DangerOutlined = Template.bind({});
DangerOutlined.args = {
children: 'Button',
variant: 'outlined',
color: 'error',
};
export const DangerBorderless = Template.bind({});
DangerBorderless.args = {
children: 'Button',
variant: 'borderless',
color: 'error',
};
export const Small = Template.bind({});
Small.args = {
children: 'Button',
variant: 'contained',
color: 'primary',
size: 'small',
};
export const Large = Template.bind({});
Large.args = {
children: 'Button',
variant: 'contained',
color: 'primary',
size: 'large',
};
export const WithStartIcon = Template.bind({});
WithStartIcon.args = {
children: 'Button',
variant: 'contained',
color: 'primary',
startIcon: <PlusIcon />,
};
export const WithEndIcon = Template.bind({});
WithEndIcon.args = {
children: 'Button',
variant: 'contained',
color: 'primary',
endIcon: <PlusCircleIcon />,
};

View File

@@ -1,86 +0,0 @@
import { XIcon } from '@/components/ui/v2/icons/XIcon';
import type { ComponentMeta, ComponentStory } from '@storybook/react';
import type { ChipProps } from './Chip';
import Chip from './Chip';
export default {
title: 'UI Library / Chip',
component: Chip,
argTypes: {
variant: {
options: ['contained', 'outlined'],
control: { type: 'radio' },
},
color: {
options: ['primary', 'secondary', 'error', 'info'],
control: { type: 'radio' },
},
disabled: {
control: { type: 'boolean' },
},
size: {
options: ['small', 'medium'],
control: { type: 'radio' },
},
},
} as ComponentMeta<typeof Chip>;
const Template: ComponentStory<typeof Chip> = function Template(
args: ChipProps,
) {
return <Chip {...args} />;
};
export const Primary = Template.bind({});
Primary.args = {
label: 'Chip',
color: 'primary',
};
export const PrimaryOutlined = Template.bind({});
PrimaryOutlined.args = {
label: 'Chip',
variant: 'outlined',
color: 'primary',
};
export const Secondary = Template.bind({});
Secondary.args = {
label: 'Chip',
color: 'secondary',
};
export const SecondaryOutlined = Template.bind({});
SecondaryOutlined.args = {
label: 'Chip',
variant: 'outlined',
color: 'secondary',
};
export const Danger = Template.bind({});
Danger.args = {
label: 'Chip',
color: 'error',
};
export const DangerOutlined = Template.bind({});
DangerOutlined.args = {
label: 'Chip',
variant: 'outlined',
color: 'error',
};
export const Small = Template.bind({});
Small.args = {
label: 'Chip',
color: 'primary',
size: 'small',
};
export const WithDeleteIcon = Template.bind({});
WithDeleteIcon.args = {
label: 'Chip',
color: 'primary',
deleteIcon: <XIcon />,
onDelete: () => {},
};

View File

@@ -1,38 +0,0 @@
import { Option } from '@/components/ui/v2/Option';
import type { Meta, StoryFn } from '@storybook/react';
import type { SelectProps } from './Select';
import Select from './Select';
export default {
title: 'UI Library / Select',
component: Select,
argTypes: {},
} as Meta<typeof Select>;
const Template: StoryFn<SelectProps<any>> = function TemplateFunction(args) {
return (
<Select className="w-64" {...args}>
<Option value="value1">Value 1</Option>
<Option value="value2">Value 2</Option>
<Option value="value3">Value 3</Option>
<Option value="value4">Value 4</Option>
</Select>
);
};
export const Default = Template.bind({});
Default.args = {
defaultValue: 'value1',
};
export const WithLabel = Template.bind({});
WithLabel.args = {
label: 'Label',
};
export const Disabled = Template.bind({});
Disabled.args = {
label: 'Label',
disabled: true,
defaultValue: 'value1',
};

View File

@@ -1,28 +0,0 @@
import type { Meta, StoryFn } from '@storybook/react';
import type { SwitchProps } from './Switch';
import Switch from './Switch';
export default {
title: 'UI Library / Switch',
component: Switch,
argTypes: {},
} as Meta<typeof Switch>;
const Template: StoryFn<SwitchProps> = function TemplateFunction(
args: SwitchProps,
) {
return <Switch label="Accept Rules" {...args} />;
};
export const Default = Template.bind({});
Default.args = {};
export const Checked = Template.bind({});
Checked.args = {
checked: true,
};
export const Disabled = Template.bind({});
Disabled.args = {
disabled: true,
};

View File

@@ -1,126 +0,0 @@
import { Form } from '@/components/form/Form';
import { Button } from '@/components/ui/v2/Button';
import { Text } from '@/components/ui/v2/Text';
import hasuraMetadataQuery from '@/tests/msw/mocks/rest/hasuraMetadataQuery';
import tableQuery from '@/tests/msw/mocks/rest/tableQuery';
import tokenQuery from '@/tests/msw/mocks/rest/tokenQuery';
import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import type { ColumnAutocompleteProps } from './ColumnAutocomplete';
import ColumnAutocomplete from './ColumnAutocomplete';
export default {
title: 'Data Browser / ColumnAutocomplete',
component: ColumnAutocomplete,
parameters: {
docs: {
source: {
type: 'code',
},
},
},
} as ComponentMeta<typeof ColumnAutocomplete>;
const defaultParameters = {
nextRouter: {
path: '/[workspaceSlug]/[appSlug]/database/browser/[dataSourceSlug]/[schemaSlug]/[tableSlug]',
asPath: '/workspace/app/database/browser/default/public/users',
query: {
workspaceSlug: 'workspace',
appSlug: 'app',
dataSourceSlug: 'default',
schemaSlug: 'public',
tableSlug: 'books',
},
},
msw: {
handlers: [tokenQuery, tableQuery, hasuraMetadataQuery],
},
};
const Template: ComponentStory<typeof ColumnAutocomplete> = function Template(
args: ColumnAutocompleteProps,
) {
const [submittedValues, setSubmittedValues] = useState<string>('');
const form = useForm<{ firstReference: string; secondReference: string }>({
defaultValues: {
firstReference: null as any,
secondReference: null as any,
},
});
function handleSubmit(values: {
firstReference: string;
secondReference: string;
}) {
setSubmittedValues(JSON.stringify(values, null, 2));
}
return (
<div className="grid grid-flow-row gap-2">
<FormProvider {...form}>
<Form onSubmit={handleSubmit} className="grid grid-flow-row gap-2">
<ColumnAutocomplete
{...args}
onChange={(newValue) =>
form.setValue('firstReference', newValue.value, {
shouldDirty: true,
})
}
onInitialized={(newValue) => {
form.setValue('firstReference', newValue.value, {
shouldDirty: true,
});
}}
/>
<ColumnAutocomplete
{...args}
onChange={(newValue) =>
form.setValue('secondReference', newValue.value, {
shouldDirty: true,
})
}
onInitialized={(newValue) => {
form.setValue('secondReference', newValue.value, {
shouldDirty: true,
});
}}
/>
<Button type="submit" className="justify-self-start">
Submit
</Button>
</Form>
</FormProvider>
<Text component="pre" className="!font-mono !text-gray-700">
{submittedValues || 'The form has not been submitted yet.'}
</Text>
</div>
);
};
export const Basic = Template.bind({});
Basic.args = {
schema: 'public',
table: 'books',
};
Basic.parameters = defaultParameters;
export const DefaultValue = Template.bind({});
DefaultValue.args = {
schema: 'public',
table: 'books',
value: 'author.id',
};
DefaultValue.parameters = defaultParameters;
export const DisabledRelationships = Template.bind({});
DisabledRelationships.args = {
schema: 'public',
table: 'books',
disableRelationships: true,
};
DisabledRelationships.parameters = defaultParameters;

View File

@@ -127,6 +127,10 @@ describe('RowPermissionsSection', () => {
process.env.NEXT_PUBLIC_ENV = 'dev';
server.listen();
});
beforeEach(() => {
server.restoreHandlers();
server.resetHandlers();
});
afterAll(() => {
server.close();

View File

@@ -1,90 +0,0 @@
import { Form } from '@/components/form/Form';
import { Button } from '@/components/ui/v2/Button';
import { Text } from '@/components/ui/v2/Text';
import type { RuleGroup } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import permissionVariablesQuery from '@/tests/msw/mocks/graphql/permissionVariablesQuery';
import hasuraMetadataQuery from '@/tests/msw/mocks/rest/hasuraMetadataQuery';
import tableQuery from '@/tests/msw/mocks/rest/tableQuery';
import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import type { RuleGroupEditorProps } from './RuleGroupEditor';
import RuleGroupEditor from './RuleGroupEditor';
export default {
title: 'Data Browser / RuleGroupEditor',
component: RuleGroupEditor,
parameters: {
docs: {
source: {
type: 'code',
},
},
},
} as ComponentMeta<typeof RuleGroupEditor>;
const defaultParameters = {
nextRouter: {
path: '/[workspaceSlug]/[appSlug]/database/browser/[dataSourceSlug]/[schemaSlug]/[tableSlug]',
asPath: '/workspace/app/database/browser/default/public/users',
query: {
workspaceSlug: 'workspace',
appSlug: 'app',
dataSourceSlug: 'default',
schemaSlug: 'public',
tableSlug: 'books',
},
},
msw: {
handlers: [tableQuery, hasuraMetadataQuery, permissionVariablesQuery],
},
};
const Template: ComponentStory<typeof RuleGroupEditor> = function Template(
args: RuleGroupEditorProps,
) {
const [submittedValues, setSubmittedValues] = useState<string>();
const form = useForm<{ ruleGroupEditor: RuleGroup }>({
defaultValues: {
ruleGroupEditor: {
operator: '_and',
rules: [{ column: '', operator: '_eq', value: '' }],
groups: [],
},
},
reValidateMode: 'onSubmit',
});
function handleSubmit(values: { ruleGroupEditor: RuleGroup }) {
setSubmittedValues(JSON.stringify(values, null, 2));
}
// note: Storybook passes `onRemove` as a prop, but we don't want to use it
return (
<div className="grid grid-flow-row gap-2">
<FormProvider {...form}>
<Form onSubmit={handleSubmit} className="grid grid-flow-row gap-2">
<RuleGroupEditor
{...args}
schema="public"
table="books"
name="ruleGroupEditor"
/>
<Button type="submit" className="justify-self-start">
Submit
</Button>
</Form>
</FormProvider>
<Text component="pre" className="!font-mono !text-gray-700">
{submittedValues || 'The form has not been submitted yet.'}
</Text>
</div>
);
};
export const Default = Template.bind({});
Default.args = {};
Default.parameters = defaultParameters;

View File

@@ -1,4 +1,4 @@
import { rest } from 'msw';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import type { ManagePermissionOptions } from './managePermission';
import managePermission from './managePermission';
@@ -12,9 +12,7 @@ const defaultParameters: ManagePermissionOptions = {
};
const server = setupServer(
rest.post('http://localhost:1337/v1/metadata', (_req, res, ctx) =>
res(ctx.json({})),
),
http.post('http://localhost:1337/v1/metadata', () => HttpResponse.json({})),
);
beforeAll(() => server.listen());

View File

@@ -1,5 +1,5 @@
import type { HasuraMetadata } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import { rest } from 'msw';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import prepareTrackForeignKeyRelationsMetadata from './prepareTrackForeignKeyRelationsMetadata';
@@ -28,11 +28,8 @@ const testMetadataResponse: { metadata: HasuraMetadata } = {
};
const metadataHandlers = [
rest.post(`${APP_URL}/v1/metadata`, (_req, res, ctx) =>
res(
ctx.status(200),
ctx.json<{ metadata: HasuraMetadata }>(testMetadataResponse),
),
http.post(`${APP_URL}/v1/metadata`, () =>
HttpResponse.json(testMetadataResponse),
),
];
@@ -131,56 +128,53 @@ test('should only prepare a one-to-one relationship if the table does not have a
test('should drop existing relationships and prepare a new one-to-many relationship', async () => {
server.use(
rest.post(`${APP_URL}/v1/metadata`, (_req, res, ctx) =>
res(
ctx.status(200),
ctx.json<{ metadata: HasuraMetadata }>({
...testMetadataResponse,
metadata: {
...testMetadataResponse.metadata,
sources: [
{
...testMetadataResponse.metadata.sources[0],
tables: [
{
...testMetadataResponse.metadata.sources[0].tables[0],
object_relationships: [
{
name: 'author',
using: {
foreign_key_constraint_on: 'author_id',
},
http.post(`${APP_URL}/v1/metadata`, () =>
HttpResponse.json({
...testMetadataResponse,
metadata: {
...testMetadataResponse.metadata,
sources: [
{
...testMetadataResponse.metadata.sources[0],
tables: [
{
...testMetadataResponse.metadata.sources[0].tables[0],
object_relationships: [
{
name: 'author',
using: {
foreign_key_constraint_on: 'author_id',
},
],
},
{
table: {
name: 'authors',
schema: 'public',
},
configuration: {},
array_relationships: [
{
name: 'books',
using: {
foreign_key_constraint_on: {
column: 'author_id',
table: {
name: 'books',
schema: 'public',
},
],
},
{
table: {
name: 'authors',
schema: 'public',
},
configuration: {},
array_relationships: [
{
name: 'books',
using: {
foreign_key_constraint_on: {
column: 'author_id',
table: {
name: 'books',
schema: 'public',
},
},
},
],
object_relationships: [],
},
],
},
],
},
}),
),
},
],
object_relationships: [],
},
],
},
],
},
}),
),
);

View File

@@ -3,6 +3,7 @@ import { render, screen, TestUserEvent } from '@/tests/testUtils';
import { vi } from 'vitest';
import DatabasePiTRSettings from './DatabasePiTRSettings';
import { getOrganizations } from '@/tests/msw/mocks/graphql/getOrganizationQuery';
import { getProjectQuery } from '@/tests/msw/mocks/graphql/getProjectQuery';
import tokenQuery from '@/tests/msw/mocks/rest/tokenQuery';
import { setupServer } from 'msw/node';
@@ -75,7 +76,7 @@ vi.mock('@/features/orgs/components/common/TransferProjectDialog', async () => {
};
});
const server = setupServer(tokenQuery);
const server = setupServer(tokenQuery, getOrganizations);
describe('DatabasePiTRSettings', () => {
beforeAll(() => {

View File

@@ -52,11 +52,11 @@ function UpgradeNotification({ description }: Props) {
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
</Link>
<OpenTransferDialogButton onClick={handleTransferDialogOpen} />
<TransferProjectDialog
open={transferProjectDialogOpen}
setOpen={setTransferProjectDialogOpen}
/>
</Text>
<TransferProjectDialog
open={transferProjectDialogOpen}
setOpen={setTransferProjectDialogOpen}
/>
</Alert>
);
}

View File

@@ -20,6 +20,17 @@ const mockServices = [
'job-backup',
];
Object.defineProperty(HTMLElement.prototype, 'getBoundingClientRect', {
value: vi.fn(() => ({
width: 100,
height: 40,
top: 0,
left: 0,
bottom: 40,
right: 100,
})),
});
vi.mock('@/features/orgs/projects/hooks/useProject', async () => ({
useProject: () => ({ project: mockApplication }),
}));

View File

@@ -1,15 +1,15 @@
import tokenQuery from '@/tests/msw/mocks/rest/tokenQuery';
import { render, screen, waitFor } from '@/tests/testUtils';
import { graphql } from 'msw';
import { HttpResponse, graphql } from 'msw';
import { setupServer } from 'msw/node';
import { beforeAll, expect, test, vi } from 'vitest';
import HasuraCorsDomainSettings from './HasuraCorsDomainSettings';
const server = setupServer(
tokenQuery,
graphql.query('GetHasuraSettings', (_req, res, ctx) =>
res(
ctx.data({
graphql.query('GetHasuraSettings', () =>
HttpResponse.json({
data: {
config: {
id: 'HasuraSettings',
__typename: 'HasuraSettings',
@@ -29,8 +29,8 @@ const server = setupServer(
resources: [],
},
},
}),
),
},
}),
),
);
@@ -62,9 +62,9 @@ describe('HasuraCorsDomainSettings', () => {
test('should enable switch by default when CORS domain is set to one or more domains', async () => {
server.use(
graphql.query('GetHasuraSettings', (_req, res, ctx) =>
res(
ctx.data({
graphql.query('GetHasuraSettings', () =>
HttpResponse.json({
data: {
config: {
id: 'HasuraSettings',
__typename: 'HasuraSettings',
@@ -84,8 +84,8 @@ describe('HasuraCorsDomainSettings', () => {
resources: [],
},
},
}),
),
},
}),
),
);

View File

@@ -82,6 +82,7 @@ describe('useProjectLogs - Subscription Creation & Cleanup', () => {
loading: false,
error: undefined,
refetch: vi.fn(),
projectNotFound: false,
});
// Mock subscribeToMore to return an unsubscribe function
@@ -133,6 +134,7 @@ describe('useProjectLogs - Subscription Creation & Cleanup', () => {
loading: true,
error: undefined,
refetch: vi.fn(),
projectNotFound: false,
});
renderHook(() => useProjectLogs(defaultProps));
@@ -146,6 +148,7 @@ describe('useProjectLogs - Subscription Creation & Cleanup', () => {
loading: false,
error: undefined,
refetch: vi.fn(),
projectNotFound: false,
});
renderHook(() => useProjectLogs(defaultProps));

View File

@@ -20,6 +20,17 @@ const mockServices = [
'job-backup',
];
Object.defineProperty(HTMLElement.prototype, 'getBoundingClientRect', {
value: vi.fn(() => ({
width: 100,
height: 40,
top: 0,
left: 0,
bottom: 40,
right: 100,
})),
});
vi.mock('@/features/orgs/projects/hooks/useProject', async () => ({
useProject: () => ({ project: mockApplication }),
}));

View File

@@ -1,7 +1,7 @@
import { mockApplication, mockOrganization } from '@/tests/mocks';
import tokenQuery from '@/tests/msw/mocks/rest/tokenQuery';
import { queryClient, render, screen } from '@/tests/testUtils';
import { rest } from 'msw';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import { afterAll, beforeAll, vi } from 'vitest';
import OverviewDeployments from './OverviewDeployments';
@@ -36,8 +36,9 @@ vi.mock('next/router', () => ({
const server = setupServer(
tokenQuery,
rest.get('https://local.graphql.local.nhost.run/v1', (_req, res, ctx) =>
res(ctx.status(200)),
http.get(
'https://local.graphql.local.nhost.run/v1',
() => new HttpResponse(null, { status: 200 }),
),
);
@@ -49,8 +50,9 @@ beforeAll(() => {
afterEach(() => {
server.resetHandlers(
rest.get('https://local.graphql.local.nhost.run/v1', (_req, res, ctx) =>
res(ctx.status(200)),
http.get(
'https://local.graphql.local.nhost.run/v1',
() => new HttpResponse(null, { status: 200 }),
),
);
queryClient.clear();
@@ -63,37 +65,31 @@ afterAll(() => {
test('should render an empty state when GitHub is not connected', async () => {
server.use(
rest.post(
http.post(
'https://local.graphql.local.nhost.run/v1',
async (req, res, ctx) => {
const { operationName } = await req.json();
async ({ request }) => {
const { operationName } = (await request.json()) as any;
if (operationName === 'getProject') {
return res(
ctx.json({
data: {
apps: [{ ...mockApplication, githubRepository: null }],
},
}),
);
return HttpResponse.json({
data: {
apps: [{ ...mockApplication, githubRepository: null }],
},
});
}
if (operationName === 'getOrganization') {
return res(
ctx.json({
data: {
organizations: [{ ...mockOrganization }],
},
}),
);
return HttpResponse.json({
data: {
organizations: [{ ...mockOrganization }],
},
});
}
return res(
ctx.json({
data: {
deployments: [],
},
}),
);
return HttpResponse.json({
data: {
deployments: [],
},
});
},
),
);
@@ -107,32 +103,28 @@ test('should render an empty state when GitHub is not connected', async () => {
});
test('should render an empty state when GitHub is connected, but there are no deployments', async () => {
server.use(
rest.post(
http.post(
'https://local.graphql.local.nhost.run/v1',
async (_req, res, ctx) => {
const { operationName } = await _req.json();
async ({ request }) => {
const { operationName } = (await request.json()) as any;
if (operationName === 'getProject') {
return res(
ctx.json({
data: {
apps: [{ ...mockApplication }],
},
}),
);
return HttpResponse.json({
data: {
apps: [{ ...mockApplication }],
},
});
}
if (operationName === 'getOrganization') {
return res(
ctx.json({
data: {
organizations: [{ ...mockOrganization }],
},
}),
);
return HttpResponse.json({
data: {
organizations: [{ ...mockOrganization }],
},
});
}
return res(ctx.json({ data: { deployments: [] } }));
return HttpResponse.json({ data: { deployments: [] } });
},
),
);
@@ -155,52 +147,46 @@ test('should render an empty state when GitHub is connected, but there are no de
test('should render a list of deployments', async () => {
server.use(
tokenQuery,
rest.post(
http.post(
'https://local.graphql.local.nhost.run/v1',
async (_req, res, ctx) => {
const { operationName } = await _req.json();
async ({ request }) => {
const { operationName } = (await request.json()) as any;
if (operationName === 'ScheduledOrPendingDeploymentsSub') {
return res(ctx.json({ data: { deployments: [] } }));
return HttpResponse.json({ data: { deployments: [] } });
}
if (operationName === 'getProject') {
return res(
ctx.json({
data: {
apps: [{ ...mockApplication }],
},
}),
);
return HttpResponse.json({
data: {
apps: [{ ...mockApplication }],
},
});
}
if (operationName === 'getOrganization') {
return res(
ctx.json({
data: {
organizations: [{ ...mockOrganization }],
},
}),
);
return HttpResponse.json({
data: {
organizations: [{ ...mockOrganization }],
},
});
}
return res(
ctx.json({
data: {
deployments: [
{
id: '1',
commitSHA: 'abc123',
deploymentStartedAt: '2021-08-01T00:00:00.000Z',
deploymentEndedAt: '2021-08-01T00:05:00.000Z',
deploymentStatus: 'DEPLOYED',
commitUserName: 'test.user',
commitUserAvatarUrl: 'http://images.example.com/avatar.png',
commitMessage: 'Test commit message',
},
],
},
}),
);
return HttpResponse.json({
data: {
deployments: [
{
id: '1',
commitSHA: 'abc123',
deploymentStartedAt: '2021-08-01T00:00:00.000Z',
deploymentEndedAt: '2021-08-01T00:05:00.000Z',
deploymentStatus: 'DEPLOYED',
commitUserName: 'test.user',
commitUserAvatarUrl: 'http://images.example.com/avatar.png',
commitMessage: 'Test commit message',
},
],
},
});
},
),
);
@@ -227,69 +213,61 @@ test('should render a list of deployments', async () => {
test('should disable redeployments if a deployment is already in progress', async () => {
server.use(
tokenQuery,
rest.post(
http.post(
'https://local.graphql.local.nhost.run/v1',
async (req, res, ctx) => {
const { operationName } = await req.json();
async ({ request }) => {
const { operationName } = (await request.json()) as any;
if (operationName === 'ScheduledOrPendingDeploymentsSub') {
return res(
ctx.json({
data: {
deployments: [
{
id: '2',
commitSHA: 'abc234',
deploymentStartedAt: '2021-08-02T00:00:00.000Z',
deploymentEndedAt: null,
deploymentStatus: 'PENDING',
commitUserName: 'test.user',
commitUserAvatarUrl: 'http://images.example.com/avatar.png',
commitMessage: 'Test commit message',
},
],
},
}),
);
}
if (operationName === 'getProject') {
return res(
ctx.json({
data: {
apps: [{ ...mockApplication }],
},
}),
);
}
if (operationName === 'getOrganization') {
return res(
ctx.json({
data: {
organizations: [{ ...mockOrganization }],
},
}),
);
}
return res(
ctx.json({
return HttpResponse.json({
data: {
deployments: [
{
id: '1',
commitSHA: 'abc123',
deploymentStartedAt: '2021-08-01T00:00:00.000Z',
deploymentEndedAt: '2021-08-01T00:05:00.000Z',
deploymentStatus: 'DEPLOYED',
id: '2',
commitSHA: 'abc234',
deploymentStartedAt: '2021-08-02T00:00:00.000Z',
deploymentEndedAt: null,
deploymentStatus: 'PENDING',
commitUserName: 'test.user',
commitUserAvatarUrl: 'http://images.example.com/avatar.png',
commitMessage: 'Test commit message',
},
],
},
}),
);
});
}
if (operationName === 'getProject') {
return HttpResponse.json({
data: {
apps: [{ ...mockApplication }],
},
});
}
if (operationName === 'getOrganization') {
return HttpResponse.json({
data: {
organizations: [{ ...mockOrganization }],
},
});
}
return HttpResponse.json({
data: {
deployments: [
{
id: '1',
commitSHA: 'abc123',
deploymentStartedAt: '2021-08-01T00:00:00.000Z',
deploymentEndedAt: '2021-08-01T00:05:00.000Z',
deploymentStatus: 'DEPLOYED',
commitUserName: 'test.user',
commitUserAvatarUrl: 'http://images.example.com/avatar.png',
commitMessage: 'Test commit message',
},
],
},
});
},
),
);

View File

@@ -1,22 +1,15 @@
import { mockOrganization, mockOrganizations } from '@/tests/mocks';
import { HttpResponse } from 'msw';
import nhostGraphQLLink from './nhostGraphQLLink';
export const getOrganizations = nhostGraphQLLink.query(
'getOrganizations',
(_req, res, ctx) =>
res(
ctx.data({
organizations: mockOrganizations,
}),
),
export const getOrganizations = nhostGraphQLLink.query('getOrganizations', () =>
HttpResponse.json({
data: { organizations: mockOrganizations },
}),
);
export const getOrganization = nhostGraphQLLink.query(
'getOrganization',
(_req, res, ctx) =>
res(
ctx.data({
organizations: [{ ...mockOrganization }],
}),
),
export const getOrganization = nhostGraphQLLink.query('getOrganization', () =>
HttpResponse.json({
data: { organizations: [{ ...mockOrganization }] },
}),
);

View File

@@ -1,10 +1,11 @@
import { HttpResponse } from 'msw';
import nhostGraphQLLink from './nhostGraphQLLink';
export const getPostgresSettings = nhostGraphQLLink.query(
'GetPostgresSettings',
(_req, res, ctx) =>
res(
ctx.data({
() =>
HttpResponse.json({
data: {
systemConfig: {
postgres: {
database: 'gnlivtcgjxctuujxpslj',
@@ -29,15 +30,15 @@ export const getPostgresSettings = nhostGraphQLLink.query(
__typename: 'ConfigPostgres',
},
},
}),
),
},
}),
);
export const getPiTRNotEnabledPostgresSettings = nhostGraphQLLink.query(
'GetPostgresSettings',
(_req, res, ctx) =>
res(
ctx.data({
() =>
HttpResponse.json({
data: {
systemConfig: {
postgres: {
database: 'gnlivtcgjxctuujxpslj',
@@ -62,8 +63,6 @@ export const getPiTRNotEnabledPostgresSettings = nhostGraphQLLink.query(
__typename: 'ConfigPostgres',
},
},
}),
),
},
}),
);
// {"data":}

View File

@@ -1,36 +1,35 @@
import { mockApplication } from '@/tests/mocks';
import { HttpResponse } from 'msw';
import nhostGraphQLLink from './nhostGraphQLLink';
export const getProjectQuery = nhostGraphQLLink.query(
'getProject',
(_req, res, ctx) =>
res(
ctx.data({
apps: [{ ...mockApplication, githubRepository: null }],
}),
),
export const getProjectQuery = nhostGraphQLLink.query('getProject', () =>
HttpResponse.json({
data: {
apps: [{ ...mockApplication, githubRepository: null }],
},
}),
);
export const getProjectStateQuery = (appStates?: any) =>
nhostGraphQLLink.query('getProjectState', (_req, res, ctx) =>
res(
ctx.data({
nhostGraphQLLink.query('getProjectState', () =>
HttpResponse.json({
data: {
apps: [
{
...mockApplication,
appStates: appStates || mockApplication.appStates,
},
],
}),
),
},
}),
);
export const getNotFoundProjectStateQuery = nhostGraphQLLink.query(
'getProjectState',
(_req, res, ctx) =>
res(
ctx.data({
() =>
HttpResponse.json({
data: {
apps: [],
}),
),
},
}),
);

View File

@@ -1,144 +1,141 @@
import { HttpResponse } from 'msw';
import nhostGraphQLLink from './nhostGraphQLLink';
export const getProjectsQuery = nhostGraphQLLink.query(
'getProjects',
(_req, res, ctx) =>
res(
ctx.data({
apps: [
{
id: 'pitr-usa-id',
name: 'pitr-not-enabled-usa',
slug: 'pitr-not-enabled-usa',
createdAt: '2025-03-10T12:35:23.193578+00:00',
subdomain: 'ocrnpctsphttfxkuefyx',
region: {
id: '1',
name: 'us-east-1',
__typename: 'regions',
},
deployments: [],
creator: {
id: 'creator-r-elek-id',
email: 'robert@elek.com',
displayName: 'Robert',
__typename: 'users',
},
appStates: [
{
id: 'cd2b77ac-3ef1-4a76-819b-ff1caca09213',
appId: 'pitr-usa-id',
message:
'failed to get dns manager: unknown region: 55985cd4-af14-4d2a-90a5-2a1253ebc1db',
stateId: 8,
createdAt: '2025-03-10T12:39:23.734345+00:00',
__typename: 'appStateHistory',
},
],
__typename: 'apps',
export const getProjectsQuery = nhostGraphQLLink.query('getProjects', () =>
HttpResponse.json({
data: {
apps: [
{
id: 'pitr-usa-id',
name: 'pitr-not-enabled-usa',
slug: 'pitr-not-enabled-usa',
createdAt: '2025-03-10T12:35:23.193578+00:00',
subdomain: 'ocrnpctsphttfxkuefyx',
region: {
id: '1',
name: 'us-east-1',
__typename: 'regions',
},
{
id: 'pitr-region-TEST-eu-id',
name: 'pitr-region-test-eu',
slug: 'pitr-region-test-eu',
createdAt: '2025-03-10T12:45:40.813234+00:00',
subdomain: 'doszbxwibtopsbfgbjpg',
region: {
id: 'dd6f8e01-35a9-4ba6-8dc6-ed972f2db93c',
name: 'eu-central-1',
__typename: 'regions',
},
deployments: [],
creator: {
id: 'creator-r-elek-id',
email: 'robert@elek.com',
displayName: 'Robert',
__typename: 'users',
},
appStates: [
{
id: 'c7fbf7ad-b60c-432b-86c2-5a9509054c47',
appId: 'pitr-region-TEST-eu-id',
message: '',
stateId: 5,
createdAt: '2025-03-12T11:08:59.926611+00:00',
__typename: 'appStateHistory',
},
],
__typename: 'apps',
deployments: [],
creator: {
id: 'creator-r-elek-id',
email: 'robert@elek.com',
displayName: 'Robert',
__typename: 'users',
},
{
id: 'pitr-test-id',
name: 'pitr-test',
slug: 'pitr-test',
createdAt: '2025-03-04T13:48:59.76498+00:00',
subdomain: 'gnlivtcgjxctuujxpslj',
region: {
id: '1',
name: 'us-east-1',
__typename: 'regions',
appStates: [
{
id: 'cd2b77ac-3ef1-4a76-819b-ff1caca09213',
appId: 'pitr-usa-id',
message:
'failed to get dns manager: unknown region: 55985cd4-af14-4d2a-90a5-2a1253ebc1db',
stateId: 8,
createdAt: '2025-03-10T12:39:23.734345+00:00',
__typename: 'appStateHistory',
},
deployments: [],
creator: {
id: 'creator-d-elek-id',
email: 'dbarrosop@dravetech.com',
displayName: 'David Elek',
__typename: 'users',
},
appStates: [
{
id: 'fc344bc6-1c59-447a-813f-e0f65754b0e0',
appId: 'pitr-test-id',
message:
'failed to deploy application to kubernetes: failed to deploy application: failed to check rollout status: error running kubectl: exit status 1',
stateId: 8,
createdAt: '2025-03-11T15:34:41.25304+00:00',
__typename: 'appStateHistory',
},
],
__typename: 'apps',
],
__typename: 'apps',
},
{
id: 'pitr-region-TEST-eu-id',
name: 'pitr-region-test-eu',
slug: 'pitr-region-test-eu',
createdAt: '2025-03-10T12:45:40.813234+00:00',
subdomain: 'doszbxwibtopsbfgbjpg',
region: {
id: 'dd6f8e01-35a9-4ba6-8dc6-ed972f2db93c',
name: 'eu-central-1',
__typename: 'regions',
},
{
id: 'pitr14-id',
name: 'pitr14',
slug: 'pitr14',
createdAt: '2025-02-25T08:55:22.82937+00:00',
subdomain: 'jqumebxpocjytrhevonb',
region: {
id: '1',
name: 'us-east-1',
__typename: 'regions',
},
deployments: [],
creator: {
id: 'creator-d-elek-id',
email: 'david@elek.com',
displayName: 'David Elek',
__typename: 'users',
},
appStates: [
{
id: '04bc2db3-a948-48fb-b674-7a8a0133dd2b',
appId: 'pitr14-id',
message: '',
stateId: 5,
createdAt: '2025-03-11T20:47:03.102948+00:00',
__typename: 'appStateHistory',
},
],
__typename: 'apps',
deployments: [],
creator: {
id: 'creator-r-elek-id',
email: 'robert@elek.com',
displayName: 'Robert',
__typename: 'users',
},
],
}),
),
appStates: [
{
id: 'c7fbf7ad-b60c-432b-86c2-5a9509054c47',
appId: 'pitr-region-TEST-eu-id',
message: '',
stateId: 5,
createdAt: '2025-03-12T11:08:59.926611+00:00',
__typename: 'appStateHistory',
},
],
__typename: 'apps',
},
{
id: 'pitr-test-id',
name: 'pitr-test',
slug: 'pitr-test',
createdAt: '2025-03-04T13:48:59.76498+00:00',
subdomain: 'gnlivtcgjxctuujxpslj',
region: {
id: '1',
name: 'us-east-1',
__typename: 'regions',
},
deployments: [],
creator: {
id: 'creator-d-elek-id',
email: 'dbarrosop@dravetech.com',
displayName: 'David Elek',
__typename: 'users',
},
appStates: [
{
id: 'fc344bc6-1c59-447a-813f-e0f65754b0e0',
appId: 'pitr-test-id',
message:
'failed to deploy application to kubernetes: failed to deploy application: failed to check rollout status: error running kubectl: exit status 1',
stateId: 8,
createdAt: '2025-03-11T15:34:41.25304+00:00',
__typename: 'appStateHistory',
},
],
__typename: 'apps',
},
{
id: 'pitr14-id',
name: 'pitr14',
slug: 'pitr14',
createdAt: '2025-02-25T08:55:22.82937+00:00',
subdomain: 'jqumebxpocjytrhevonb',
region: {
id: '1',
name: 'us-east-1',
__typename: 'regions',
},
deployments: [],
creator: {
id: 'creator-d-elek-id',
email: 'david@elek.com',
displayName: 'David Elek',
__typename: 'users',
},
appStates: [
{
id: '04bc2db3-a948-48fb-b674-7a8a0133dd2b',
appId: 'pitr14-id',
message: '',
stateId: 5,
createdAt: '2025-03-11T20:47:03.102948+00:00',
__typename: 'appStateHistory',
},
],
__typename: 'apps',
},
],
},
}),
);
export const getEmptyProjectsQuery = nhostGraphQLLink.query(
'getProjects',
(_req, res, ctx) =>
res(
ctx.data({
apps: [],
}),
),
export const getEmptyProjectsQuery = nhostGraphQLLink.query('getProjects', () =>
HttpResponse.json({
data: {
apps: [],
},
}),
);

View File

@@ -1,15 +1,16 @@
import { HttpResponse } from 'msw';
import nhostGraphQLLink from './nhostGraphQLLink';
export const organizationMemberInvites = nhostGraphQLLink.query(
'organizationMemberInvites',
(_req, res, ctx) => res(ctx.data({ organizationMemberInvites: [] })),
() => HttpResponse.json({ data: { organizationMemberInvites: [] } }),
);
export const organizationNewRequests = nhostGraphQLLink.query(
'organizationNewRequests',
(_req, res, ctx) =>
res(
ctx.data({
() =>
HttpResponse.json({
data: {
organizationNewRequests: [
{
id: 'org-request-id-1',
@@ -17,6 +18,6 @@ export const organizationNewRequests = nhostGraphQLLink.query(
__typename: 'organization_new_request',
},
],
}),
),
},
}),
);

View File

@@ -1,11 +1,11 @@
import { HttpResponse } from 'msw';
import nhostGraphQLLink from './nhostGraphQLLink';
const permissionVariablesQuery = nhostGraphQLLink.query(
'GetRolesPermissions',
(_req, res, ctx) =>
res(
ctx.delay(250),
ctx.data({
async () =>
HttpResponse.json({
data: {
config: {
auth: {
user: {
@@ -32,8 +32,8 @@ const permissionVariablesQuery = nhostGraphQLLink.query(
},
},
},
}),
),
},
}),
);
export default permissionVariablesQuery;

View File

@@ -1,50 +1,46 @@
import { HttpResponse } from 'msw';
import nhostGraphQLLink from './nhostGraphQLLink';
/**
* Use this handler to simulate a query that returns only the Pro plan.
*/
export const getProPlanOnlyQuery = nhostGraphQLLink.query(
'GetPlans',
(_req, res, ctx) =>
res(
ctx.data({
plans: [
{
__typename: 'plans',
id: 'dc5e805e-1bef-4d43-809e-9fdf865e211a',
name: 'Pro',
price: 25,
isFree: false,
},
],
}),
),
export const getProPlanOnlyQuery = nhostGraphQLLink.query('GetPlans', () =>
HttpResponse.json({
data: {
plans: [
{
__typename: 'plans',
id: 'dc5e805e-1bef-4d43-809e-9fdf865e211a',
name: 'Pro',
price: 25,
isFree: false,
},
],
},
}),
);
/**
* Use this handler to simulate a query that returns all the available plans.
*/
export const getAllPlansQuery = nhostGraphQLLink.query(
'GetPlans',
(_req, res, ctx) =>
res(
ctx.data({
plans: [
{
__typename: 'plans',
id: '00000000-0000-0000-0000-000000000000',
name: 'Starter',
price: 0,
isFree: true,
},
{
__typename: 'plans',
id: '00000000-0000-0000-0000-000000000001',
name: 'Pro',
price: 25,
isFree: false,
},
],
}),
),
export const getAllPlansQuery = nhostGraphQLLink.query('GetPlans', () =>
HttpResponse.json({
data: {
plans: [
{
__typename: 'plans',
id: '00000000-0000-0000-0000-000000000000',
name: 'Starter',
price: 0,
isFree: true,
},
{
__typename: 'plans',
id: '00000000-0000-0000-0000-000000000001',
name: 'Pro',
price: 25,
isFree: false,
},
],
},
}),
);

View File

@@ -1,10 +1,11 @@
import { HttpResponse } from 'msw';
import nhostGraphQLLink from './nhostGraphQLLink';
export const prefetchNewAppQuery = nhostGraphQLLink.query(
'PrefetchNewApp',
(_req, res, ctx) =>
res(
ctx.data({
() =>
HttpResponse.json({
data: {
regions: [
{
id: 'dd6f8e01-35a9-4ba6-8dc6-ed972f2db93c',
@@ -67,6 +68,6 @@ export const prefetchNewAppQuery = nhostGraphQLLink.query(
__typename: 'plans',
},
],
}),
),
},
}),
);

View File

@@ -1,13 +1,13 @@
import { HttpResponse } from 'msw';
import nhostGraphQLLink from './nhostGraphQLLink';
/**
* Use this handler to simulate the initial state of the allocated resources.
*/
export const resourcesUnavailableQuery = nhostGraphQLLink.query(
'GetResources',
(_req, res, ctx) =>
res(
ctx.data({
() =>
HttpResponse.json({
data: {
config: {
__typename: 'ConfigConfig',
postgres: {
@@ -23,8 +23,8 @@ export const resourcesUnavailableQuery = nhostGraphQLLink.query(
resources: null,
},
},
}),
),
},
}),
);
/**
@@ -32,9 +32,9 @@ export const resourcesUnavailableQuery = nhostGraphQLLink.query(
*/
export const resourcesAvailableQuery = nhostGraphQLLink.query(
'GetResources',
(_req, res, ctx) =>
res(
ctx.data({
() =>
HttpResponse.json({
data: {
config: {
__typename: 'ConfigConfig',
postgres: {
@@ -86,8 +86,8 @@ export const resourcesAvailableQuery = nhostGraphQLLink.query(
},
},
},
}),
),
},
}),
);
/**
@@ -95,9 +95,9 @@ export const resourcesAvailableQuery = nhostGraphQLLink.query(
*/
export const resourcesUpdatedQuery = nhostGraphQLLink.query(
'GetResources',
(_req, res, ctx) =>
res(
ctx.data({
() =>
HttpResponse.json({
data: {
config: {
__typename: 'ConfigConfig',
postgres: {
@@ -137,6 +137,6 @@ export const resourcesUpdatedQuery = nhostGraphQLLink.query(
},
},
},
}),
),
},
}),
);

View File

@@ -1,11 +1,12 @@
import { HttpResponse } from 'msw';
import nhostGraphQLLink from './nhostGraphQLLink';
export default nhostGraphQLLink.mutation('UpdateConfig', (req, res, ctx) =>
res(
ctx.data({
export default nhostGraphQLLink.mutation('UpdateConfig', () =>
HttpResponse.json({
data: {
updateConfig: {
id: 'ConfigConfig',
},
}),
),
},
}),
);

View File

@@ -1,419 +1,415 @@
import { rest } from 'msw';
import { delay, http, HttpResponse } from 'msw';
const hasuraMetadataQuery = rest.post(
const hasuraMetadataQuery = http.post(
'https://local.hasura.local.nhost.run/v1/metadata',
(_req, res, ctx) =>
res(
ctx.delay(250),
ctx.json({
metadata: {
version: 3,
sources: [
{
name: 'default',
kind: 'postgres',
tables: [
{
table: { name: 'authors', schema: 'public' },
array_relationships: [
{
name: 'books',
using: {
foreign_key_constraint_on: {
column: 'author_id',
table: { name: 'books', schema: 'public' },
},
async () => {
await delay(250);
return HttpResponse.json({
metadata: {
version: 3,
sources: [
{
name: 'default',
kind: 'postgres',
tables: [
{
table: { name: 'authors', schema: 'public' },
array_relationships: [
{
name: 'books',
using: {
foreign_key_constraint_on: {
column: 'author_id',
table: { name: 'books', schema: 'public' },
},
},
],
},
{
table: { name: 'books', schema: 'public' },
object_relationships: [
{
name: 'author',
using: { foreign_key_constraint_on: 'author_id' },
},
],
},
],
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,
],
},
{
table: { name: 'books', schema: 'public' },
object_relationships: [
{
name: 'author',
using: { foreign_key_constraint_on: 'author_id' },
},
],
},
],
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,
},
},
],
},
resource_version: 10,
}),
),
},
],
},
resource_version: 10,
});
},
);
export const hasuraRelationShipsMetadataQuery = rest.post(
export const hasuraRelationShipsMetadataQuery = http.post(
'https://local.hasura.local.nhost.run/v1/metadata',
(_req, res, ctx) =>
res(
ctx.json({
resource_version: 26,
metadata: {
version: 3,
sources: [
{
name: 'default',
kind: 'postgres',
tables: [
{
table: {
name: 'country',
schema: 'public',
},
array_relationships: [
{
name: 'county',
using: {
foreign_key_constraint_on: {
column: 'countryId',
table: {
name: 'county',
schema: 'public',
},
},
},
},
],
() =>
HttpResponse.json({
resource_version: 26,
metadata: {
version: 3,
sources: [
{
name: 'default',
kind: 'postgres',
tables: [
{
table: {
name: 'country',
schema: 'public',
},
{
table: {
array_relationships: [
{
name: 'county',
schema: 'public',
},
object_relationships: [
{
name: 'country',
using: {
foreign_key_constraint_on: 'countryId',
},
},
],
array_relationships: [
{
name: 'town',
using: {
foreign_key_constraint_on: {
column: 'countyId',
table: {
name: 'town',
schema: 'public',
},
using: {
foreign_key_constraint_on: {
column: 'countryId',
table: {
name: 'county',
schema: 'public',
},
},
},
],
},
{
table: {
name: 'town',
schema: 'public',
},
object_relationships: [
{
name: 'county',
using: {
foreign_key_constraint_on: 'countyId',
],
},
{
table: {
name: 'county',
schema: 'public',
},
object_relationships: [
{
name: 'country',
using: {
foreign_key_constraint_on: 'countryId',
},
},
],
array_relationships: [
{
name: 'town',
using: {
foreign_key_constraint_on: {
column: 'countyId',
table: {
name: 'town',
schema: 'public',
},
},
},
],
},
],
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,
],
},
{
table: {
name: 'town',
schema: 'public',
},
object_relationships: [
{
name: 'county',
using: {
foreign_key_constraint_on: 'countyId',
},
},
],
},
],
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,
},
},
],
},
}),
),
},
],
},
}),
);
export const hasuraColumnMetadataQuery = rest.post(
export const hasuraColumnMetadataQuery = http.post(
'https://local.hasura.local.nhost.run/v1/metadata',
(_req, res, ctx) =>
res(
ctx.json({
resource_version: 389,
metadata: {
version: 3,
sources: [
{
name: 'default',
kind: 'postgres',
tables: [
{
table: {
name: 'actor',
schema: 'public',
},
object_relationships: [
{
name: 'actor_movie',
using: {
foreign_key_constraint_on: {
column: 'actor_id',
table: {
name: 'actor_movie',
schema: 'public',
},
},
},
},
],
() =>
HttpResponse.json({
resource_version: 389,
metadata: {
version: 3,
sources: [
{
name: 'default',
kind: 'postgres',
tables: [
{
table: {
name: 'actor',
schema: 'public',
},
{
table: {
object_relationships: [
{
name: 'actor_movie',
schema: 'public',
},
object_relationships: [
{
name: 'actor',
using: {
foreign_key_constraint_on: 'actor_id',
},
},
{
name: 'movie',
using: {
foreign_key_constraint_on: 'movie_id',
},
},
],
},
{
table: {
name: 'director',
schema: 'public',
},
array_relationships: [
{
name: 'movies',
using: {
foreign_key_constraint_on: {
column: 'director_id',
table: {
name: 'movies',
schema: 'public',
},
using: {
foreign_key_constraint_on: {
column: 'actor_id',
table: {
name: 'actor_movie',
schema: 'public',
},
},
},
],
},
],
},
{
table: {
name: 'actor_movie',
schema: 'public',
},
{
table: {
object_relationships: [
{
name: 'actor',
using: {
foreign_key_constraint_on: 'actor_id',
},
},
{
name: 'movie',
using: {
foreign_key_constraint_on: 'movie_id',
},
},
],
},
{
table: {
name: 'director',
schema: 'public',
},
array_relationships: [
{
name: 'movies',
schema: 'public',
},
object_relationships: [
{
name: 'author',
using: {
foreign_key_constraint_on: 'director_id',
},
},
],
array_relationships: [
{
name: 'actor_movie',
using: {
foreign_key_constraint_on: {
column: 'movie_id',
table: {
name: 'actor_movie',
schema: 'public',
},
using: {
foreign_key_constraint_on: {
column: 'director_id',
table: {
name: 'movies',
schema: 'public',
},
},
},
],
},
],
},
{
table: {
name: 'movies',
schema: 'public',
},
{
table: {
name: 'notes',
schema: 'public',
object_relationships: [
{
name: 'author',
using: {
foreign_key_constraint_on: 'director_id',
},
},
object_relationships: [
{
name: 'user',
using: {
foreign_key_constraint_on: 'owner',
},
},
],
insert_permissions: [
{
role: 'user',
permission: {
check: {
owner: {
_eq: 'X-Hasura-User-Id',
},
},
columns: ['id', 'note', 'owner'],
},
},
],
select_permissions: [
{
role: 'user',
permission: {
columns: ['id', 'note'],
filter: {
owner: {
_eq: 'X-Hasura-User-Id',
},
],
array_relationships: [
{
name: 'actor_movie',
using: {
foreign_key_constraint_on: {
column: 'movie_id',
table: {
name: 'actor_movie',
schema: 'public',
},
},
},
],
update_permissions: [
{
role: 'user',
permission: {
columns: ['note'],
filter: {
owner: {
_eq: 'X-Hasura-User-Id',
},
},
check: null,
},
},
],
delete_permissions: [
{
role: 'user',
permission: {
filter: {
id: {
_eq: 'X-Hasura-User-Id',
},
},
},
},
],
},
],
},
{
table: {
name: 'notes',
schema: 'public',
},
{
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',
object_relationships: [
{
name: 'user',
using: {
foreign_key_constraint_on: 'owner',
},
},
array_relationships: [
{
name: 'files',
using: {
foreign_key_constraint_on: {
column: 'bucket_id',
table: {
name: 'files',
schema: 'storage',
},
],
insert_permissions: [
{
role: 'user',
permission: {
check: {
owner: {
_eq: 'X-Hasura-User-Id',
},
},
columns: ['id', 'note', 'owner'],
},
},
],
select_permissions: [
{
role: 'user',
permission: {
columns: ['id', 'note'],
filter: {
owner: {
_eq: 'X-Hasura-User-Id',
},
},
},
],
},
],
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,
],
update_permissions: [
{
role: 'user',
permission: {
columns: ['note'],
filter: {
owner: {
_eq: 'X-Hasura-User-Id',
},
},
check: null,
},
},
use_prepared_statements: true,
],
delete_permissions: [
{
role: 'user',
permission: {
filter: {
id: {
_eq: 'X-Hasura-User-Id',
},
},
},
},
],
},
{
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',
},
},
},
},
],
},
],
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,
},
},
],
},
}),
),
},
],
},
}),
);
export default hasuraMetadataQuery;

View File

@@ -1,143 +1,23 @@
import { rest } from 'msw';
import { http, HttpResponse } from 'msw';
const tableQuery = rest.post(
const tableQuery = http.post(
'https://local.hasura.local.nhost.run/v2/query',
async (req, res, ctx) => {
const body = await req.json();
if (/table_name = 'authors'/gim.exec(body.args[0].args.sql) !== null) {
return res(
ctx.delay(250),
ctx.json([
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
[
'{"table_catalog":"pqfgbylcwyuertjcrmgy","table_schema":"public","table_name":"authors","column_name":"id","ordinal_position":1,"column_default":"gen_random_uuid()","is_nullable":"NO","data_type":"uuid","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"pqfgbylcwyuertjcrmgy","udt_schema":"pg_catalog","udt_name":"uuid","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"1","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","is_primary":true,"is_unique":true,"column_comment":null}',
],
[
'{"table_catalog":"pqfgbylcwyuertjcrmgy","table_schema":"public","table_name":"authors","column_name":"name","ordinal_position":2,"column_default":null,"is_nullable":"NO","data_type":"text","character_maximum_length":null,"character_octet_length":1073741824,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"pqfgbylcwyuertjcrmgy","udt_schema":"pg_catalog","udt_name":"text","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"2","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","is_primary":false,"is_unique":false,"column_comment":null}',
],
[
'{"table_catalog":"pqfgbylcwyuertjcrmgy","table_schema":"public","table_name":"authors","column_name":"birth_date","ordinal_position":3,"column_default":null,"is_nullable":"NO","data_type":"timestamp without time zone","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":6,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"pqfgbylcwyuertjcrmgy","udt_schema":"pg_catalog","udt_name":"timestamp","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"3","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","is_primary":false,"is_unique":false,"column_comment":null}',
],
],
},
{ result_type: 'TuplesOk', result: [['row_to_json']] },
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
[
'{"constraint_name":"authors_pkey","constraint_type":"p","constraint_definition":"PRIMARY KEY (id)","column_name":"id"}',
],
],
},
{ result_type: 'TuplesOk', result: [['count'], ['0']] },
]),
);
}
if (/table_name = 'town'/gim.exec(body.args[0].args.sql) !== null) {
return res(
ctx.json([
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
[
'{"table_catalog":"local","table_schema":"public","table_name":"town","column_name":"id","ordinal_position":1,"column_default":"gen_random_uuid()","is_nullable":"NO","data_type":"uuid","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"local","udt_schema":"pg_catalog","udt_name":"uuid","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"1","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","full_data_type":"uuid","is_primary":true,"is_unique":true,"column_comment":null}',
],
[
'{"table_catalog":"local","table_schema":"public","table_name":"town","column_name":"name","ordinal_position":2,"column_default":null,"is_nullable":"NO","data_type":"text","character_maximum_length":null,"character_octet_length":1073741824,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"local","udt_schema":"pg_catalog","udt_name":"text","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"2","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","full_data_type":"text","is_primary":false,"is_unique":false,"column_comment":null}',
],
[
'{"table_catalog":"local","table_schema":"public","table_name":"town","column_name":"countyId","ordinal_position":3,"column_default":null,"is_nullable":"NO","data_type":"uuid","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"local","udt_schema":"pg_catalog","udt_name":"uuid","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"3","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","full_data_type":"uuid","is_primary":false,"is_unique":false,"column_comment":null}',
],
],
},
{
result_type: 'TuplesOk',
result: [['row_to_json']],
},
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
[
'{"constraint_name":"town_countyId_fkey","constraint_type":"f","constraint_definition":"FOREIGN KEY (\\"countyId\\") REFERENCES county(id) ON UPDATE RESTRICT ON DELETE RESTRICT","column_name":"countyId"}',
],
[
'{"constraint_name":"town_pkey","constraint_type":"p","constraint_definition":"PRIMARY KEY (id)","column_name":"id"}',
],
],
},
{
result_type: 'TuplesOk',
result: [['count'], ['0']],
},
]),
);
}
if (/table_name = 'actor'/gim.exec(body.args[0].args.sql) !== null) {
return res(
ctx.json([
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
[
'{"table_catalog":"klkudrtrpapfrseiidkp","table_schema":"public","table_name":"actor","column_name":"id","ordinal_position":1,"column_default":"gen_random_uuid()","is_nullable":"NO","data_type":"uuid","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"klkudrtrpapfrseiidkp","udt_schema":"pg_catalog","udt_name":"uuid","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"1","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","full_data_type":"uuid","is_primary":true,"is_unique":true,"column_comment":null}',
],
[
'{"table_catalog":"klkudrtrpapfrseiidkp","table_schema":"public","table_name":"actor","column_name":"name","ordinal_position":2,"column_default":null,"is_nullable":"NO","data_type":"text","character_maximum_length":null,"character_octet_length":1073741824,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"klkudrtrpapfrseiidkp","udt_schema":"pg_catalog","udt_name":"text","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"2","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","full_data_type":"text","is_primary":false,"is_unique":false,"column_comment":null}',
],
],
},
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
['{"id":"1902e481-b080-4340-abe3-27b0a60973c6","name":"There"}'],
['{"id":"a486b088-50e8-41d0-88b0-5bf9a3e7b5e7","name":"hello"}'],
],
},
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
[
'{"constraint_name":"actor_pkey","constraint_type":"p","constraint_definition":"PRIMARY KEY (id)","column_name":"id"}',
],
],
},
{
result_type: 'TuplesOk',
result: [['count'], ['2']],
},
]),
);
}
return res(
ctx.delay(250),
ctx.json([
async ({ request }) => {
const body = (await request.json()) as any;
if (/table_name = 'authors'/gim.exec(body?.args?.[0].args.sql) !== null) {
return HttpResponse.json([
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
[
'{"table_catalog":"pqfgbylcwyuertjcrmgy","table_schema":"public","table_name":"books","column_name":"id","ordinal_position":1,"column_default":"gen_random_uuid()","is_nullable":"NO","data_type":"uuid","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"pqfgbylcwyuertjcrmgy","udt_schema":"pg_catalog","udt_name":"uuid","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"1","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","is_primary":true,"is_unique":true,"column_comment":null}',
'{"table_catalog":"pqfgbylcwyuertjcrmgy","table_schema":"public","table_name":"authors","column_name":"id","ordinal_position":1,"column_default":"gen_random_uuid()","is_nullable":"NO","data_type":"uuid","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"pqfgbylcwyuertjcrmgy","udt_schema":"pg_catalog","udt_name":"uuid","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"1","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","is_primary":true,"is_unique":true,"column_comment":null}',
],
[
'{"table_catalog":"pqfgbylcwyuertjcrmgy","table_schema":"public","table_name":"books","column_name":"title","ordinal_position":2,"column_default":null,"is_nullable":"NO","data_type":"text","character_maximum_length":null,"character_octet_length":1073741824,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"pqfgbylcwyuertjcrmgy","udt_schema":"pg_catalog","udt_name":"text","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"2","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","is_primary":false,"is_unique":false,"column_comment":null}',
'{"table_catalog":"pqfgbylcwyuertjcrmgy","table_schema":"public","table_name":"authors","column_name":"name","ordinal_position":2,"column_default":null,"is_nullable":"NO","data_type":"text","character_maximum_length":null,"character_octet_length":1073741824,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"pqfgbylcwyuertjcrmgy","udt_schema":"pg_catalog","udt_name":"text","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"2","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","is_primary":false,"is_unique":false,"column_comment":null}',
],
[
'{"table_catalog":"pqfgbylcwyuertjcrmgy","table_schema":"public","table_name":"books","column_name":"release_date","ordinal_position":3,"column_default":null,"is_nullable":"NO","data_type":"timestamp without time zone","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":6,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"pqfgbylcwyuertjcrmgy","udt_schema":"pg_catalog","udt_name":"timestamp","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"3","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","is_primary":false,"is_unique":false,"column_comment":null}',
],
[
'{"table_catalog":"pqfgbylcwyuertjcrmgy","table_schema":"public","table_name":"books","column_name":"author_id","ordinal_position":4,"column_default":null,"is_nullable":"NO","data_type":"uuid","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"pqfgbylcwyuertjcrmgy","udt_schema":"pg_catalog","udt_name":"uuid","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"4","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","is_primary":false,"is_unique":false,"column_comment":null}',
'{"table_catalog":"pqfgbylcwyuertjcrmgy","table_schema":"public","table_name":"authors","column_name":"birth_date","ordinal_position":3,"column_default":null,"is_nullable":"NO","data_type":"timestamp without time zone","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":6,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"pqfgbylcwyuertjcrmgy","udt_schema":"pg_catalog","udt_name":"timestamp","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"3","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","is_primary":false,"is_unique":false,"column_comment":null}',
],
],
},
@@ -147,16 +27,126 @@ const tableQuery = rest.post(
result: [
['row_to_json'],
[
'{"constraint_name":"books_author_id_fkey","constraint_type":"f","constraint_definition":"FOREIGN KEY (author_id) REFERENCES authors(id) ON UPDATE RESTRICT ON DELETE RESTRICT","column_name":"author_id"}',
],
[
'{"constraint_name":"books_pkey","constraint_type":"p","constraint_definition":"PRIMARY KEY (id)","column_name":"id"}',
'{"constraint_name":"authors_pkey","constraint_type":"p","constraint_definition":"PRIMARY KEY (id)","column_name":"id"}',
],
],
},
{ result_type: 'TuplesOk', result: [['count'], ['0']] },
]),
);
]);
}
if (/table_name = 'town'/gim.exec(body.args[0].args.sql) !== null) {
return HttpResponse.json([
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
[
'{"table_catalog":"local","table_schema":"public","table_name":"town","column_name":"id","ordinal_position":1,"column_default":"gen_random_uuid()","is_nullable":"NO","data_type":"uuid","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"local","udt_schema":"pg_catalog","udt_name":"uuid","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"1","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","full_data_type":"uuid","is_primary":true,"is_unique":true,"column_comment":null}',
],
[
'{"table_catalog":"local","table_schema":"public","table_name":"town","column_name":"name","ordinal_position":2,"column_default":null,"is_nullable":"NO","data_type":"text","character_maximum_length":null,"character_octet_length":1073741824,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"local","udt_schema":"pg_catalog","udt_name":"text","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"2","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","full_data_type":"text","is_primary":false,"is_unique":false,"column_comment":null}',
],
[
'{"table_catalog":"local","table_schema":"public","table_name":"town","column_name":"countyId","ordinal_position":3,"column_default":null,"is_nullable":"NO","data_type":"uuid","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"local","udt_schema":"pg_catalog","udt_name":"uuid","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"3","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","full_data_type":"uuid","is_primary":false,"is_unique":false,"column_comment":null}',
],
],
},
{
result_type: 'TuplesOk',
result: [['row_to_json']],
},
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
[
'{"constraint_name":"town_countyId_fkey","constraint_type":"f","constraint_definition":"FOREIGN KEY (\\"countyId\\") REFERENCES county(id) ON UPDATE RESTRICT ON DELETE RESTRICT","column_name":"countyId"}',
],
[
'{"constraint_name":"town_pkey","constraint_type":"p","constraint_definition":"PRIMARY KEY (id)","column_name":"id"}',
],
],
},
{
result_type: 'TuplesOk',
result: [['count'], ['0']],
},
]);
}
if (/table_name = 'actor'/gim.exec(body.args[0].args.sql) !== null) {
return HttpResponse.json([
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
[
'{"table_catalog":"klkudrtrpapfrseiidkp","table_schema":"public","table_name":"actor","column_name":"id","ordinal_position":1,"column_default":"gen_random_uuid()","is_nullable":"NO","data_type":"uuid","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"klkudrtrpapfrseiidkp","udt_schema":"pg_catalog","udt_name":"uuid","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"1","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","full_data_type":"uuid","is_primary":true,"is_unique":true,"column_comment":null}',
],
[
'{"table_catalog":"klkudrtrpapfrseiidkp","table_schema":"public","table_name":"actor","column_name":"name","ordinal_position":2,"column_default":null,"is_nullable":"NO","data_type":"text","character_maximum_length":null,"character_octet_length":1073741824,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"klkudrtrpapfrseiidkp","udt_schema":"pg_catalog","udt_name":"text","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"2","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","full_data_type":"text","is_primary":false,"is_unique":false,"column_comment":null}',
],
],
},
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
['{"id":"1902e481-b080-4340-abe3-27b0a60973c6","name":"There"}'],
['{"id":"a486b088-50e8-41d0-88b0-5bf9a3e7b5e7","name":"hello"}'],
],
},
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
[
'{"constraint_name":"actor_pkey","constraint_type":"p","constraint_definition":"PRIMARY KEY (id)","column_name":"id"}',
],
],
},
{
result_type: 'TuplesOk',
result: [['count'], ['2']],
},
]);
}
return HttpResponse.json([
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
[
'{"table_catalog":"pqfgbylcwyuertjcrmgy","table_schema":"public","table_name":"books","column_name":"id","ordinal_position":1,"column_default":"gen_random_uuid()","is_nullable":"NO","data_type":"uuid","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"pqfgbylcwyuertjcrmgy","udt_schema":"pg_catalog","udt_name":"uuid","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"1","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","is_primary":true,"is_unique":true,"column_comment":null}',
],
[
'{"table_catalog":"pqfgbylcwyuertjcrmgy","table_schema":"public","table_name":"books","column_name":"title","ordinal_position":2,"column_default":null,"is_nullable":"NO","data_type":"text","character_maximum_length":null,"character_octet_length":1073741824,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"pqfgbylcwyuertjcrmgy","udt_schema":"pg_catalog","udt_name":"text","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"2","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","is_primary":false,"is_unique":false,"column_comment":null}',
],
[
'{"table_catalog":"pqfgbylcwyuertjcrmgy","table_schema":"public","table_name":"books","column_name":"release_date","ordinal_position":3,"column_default":null,"is_nullable":"NO","data_type":"timestamp without time zone","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":6,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"pqfgbylcwyuertjcrmgy","udt_schema":"pg_catalog","udt_name":"timestamp","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"3","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","is_primary":false,"is_unique":false,"column_comment":null}',
],
[
'{"table_catalog":"pqfgbylcwyuertjcrmgy","table_schema":"public","table_name":"books","column_name":"author_id","ordinal_position":4,"column_default":null,"is_nullable":"NO","data_type":"uuid","character_maximum_length":null,"character_octet_length":null,"numeric_precision":null,"numeric_precision_radix":null,"numeric_scale":null,"datetime_precision":null,"interval_type":null,"interval_precision":null,"character_set_catalog":null,"character_set_schema":null,"character_set_name":null,"collation_catalog":null,"collation_schema":null,"collation_name":null,"domain_catalog":null,"domain_schema":null,"domain_name":null,"udt_catalog":"pqfgbylcwyuertjcrmgy","udt_schema":"pg_catalog","udt_name":"uuid","scope_catalog":null,"scope_schema":null,"scope_name":null,"maximum_cardinality":null,"dtd_identifier":"4","is_self_referencing":"NO","is_identity":"NO","identity_generation":null,"identity_start":null,"identity_increment":null,"identity_maximum":null,"identity_minimum":null,"identity_cycle":"NO","is_generated":"NEVER","generation_expression":null,"is_updatable":"YES","is_primary":false,"is_unique":false,"column_comment":null}',
],
],
},
{ result_type: 'TuplesOk', result: [['row_to_json']] },
{
result_type: 'TuplesOk',
result: [
['row_to_json'],
[
'{"constraint_name":"books_author_id_fkey","constraint_type":"f","constraint_definition":"FOREIGN KEY (author_id) REFERENCES authors(id) ON UPDATE RESTRICT ON DELETE RESTRICT","column_name":"author_id"}',
],
[
'{"constraint_name":"books_pkey","constraint_type":"p","constraint_definition":"PRIMARY KEY (id)","column_name":"id"}',
],
],
},
{ result_type: 'TuplesOk', result: [['count'], ['0']] },
]);
},
);

View File

@@ -1,10 +1,10 @@
import { mockSession } from '@/tests/mocks';
import type { Session } from '@nhost/nhost-js/auth';
import { rest } from 'msw';
import { http, HttpResponse } from 'msw';
const tokenQuery = rest.post(
const tokenQuery = http.post(
'https://local.auth.local.nhost.run/v1/token',
(_req, res, ctx) => res(ctx.json<Session>(mockSession)),
() => HttpResponse.json<Session>(mockSession),
);
export default tokenQuery;

View File

@@ -35,6 +35,7 @@ import userEvent, {
type Options,
type UserEvent,
} from '@testing-library/user-event';
import { HttpResponse } from 'msw';
import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtime';
import type { PropsWithChildren, ReactElement } from 'react';
import { Toaster } from 'react-hot-toast';
@@ -154,9 +155,9 @@ const graphqlRequestHandlerFactory = (
type: 'mutation' | 'query',
responsePromise: any,
) =>
nhostGraphQLLink[type](operationName, async (_req, res, ctx) => {
nhostGraphQLLink[type](operationName, async () => {
const data = await responsePromise;
return res(ctx.data(data));
return HttpResponse.json({ data });
});
/* Helper function to pause responses to be able to test loading states */
export const createGraphqlMockResolver = (

View File

@@ -9,7 +9,7 @@
"test:broken-links": "pnpm exec mintlify broken-links"
},
"devDependencies": {
"mintlify": "^4.2.87",
"mintlify": "^4.2.158",
"prettier": "^3.5.3",
"typedoc": "^0.28.4",
"typedoc-plugin-markdown": "^4.6.3"

View File

@@ -10,10 +10,10 @@ The Nhost CLI provides flexible development workflows that adapt to your needs.
## Development Workflows
<CardGroup cols={2}>
<Card title="Local Development" icon="laptop-code">
<Card title="Local Development" icon="laptop-code" href="/platform/cli/local-development">
Run the complete Nhost stack locally for offline development, fast iteration, and full control over your environment.
</Card>
<Card title="Cloud Development" icon="cloud">
<Card title="Cloud Development" icon="cloud" href="/platform/cli/cloud-development">
Develop against cloud infrastructure while maintaining local tools for simplified remote testing and team collaboration.
</Card>
</CardGroup>

1696
docs/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -590,11 +590,13 @@ This method may return different T based on the response code:
pushChainFunction(chainFunction: ChainFunction): void;
```
Add a middleware function to the fetch chain
##### Parameters
| Parameter | Type |
| --------------- | ------------------------------------------ |
| `chainFunction` | [`ChainFunction`](./fetch#chainfunction) |
| Parameter | Type | Description |
| --------------- | ------------------------------------------ | ------------------------------ |
| `chainFunction` | [`ChainFunction`](./fetch#chainfunction) | The middleware function to add |
##### Returns

View File

@@ -207,6 +207,24 @@ For a more generic request, use the `fetch` method instead.
Promise with the function response and metadata
#### pushChainFunction()
```ts
pushChainFunction(chainFunction: ChainFunction): void;
```
Add a middleware function to the fetch chain
##### Parameters
| Parameter | Type | Description |
| --------------- | ------------------------------------------ | ------------------------------ |
| `chainFunction` | [`ChainFunction`](./fetch#chainfunction) | The middleware function to add |
##### Returns
`void`
# Functions
## createAPIClient()

View File

@@ -289,6 +289,24 @@ URL for the GraphQL endpoint.
### Methods
#### pushChainFunction()
```ts
pushChainFunction(chainFunction: ChainFunction): void;
```
Add a middleware function to the fetch chain
##### Parameters
| Parameter | Type | Description |
| --------------- | ------------------------------------------ | ------------------------------ |
| `chainFunction` | [`ChainFunction`](./fetch#chainfunction) | The middleware function to add |
##### Returns
`void`
#### request()
##### Call Signature

View File

@@ -399,11 +399,13 @@ This method may return different T based on the response code:
pushChainFunction(chainFunction: ChainFunction): void;
```
Add a middleware function to the fetch chain
##### Parameters
| Parameter | Type |
| --------------- | ------------------------------------------ |
| `chainFunction` | [`ChainFunction`](./fetch#chainfunction) |
| Parameter | Type | Description |
| --------------- | ------------------------------------------ | ------------------------------ |
| `chainFunction` | [`ChainFunction`](./fetch#chainfunction) | The middleware function to add |
##### Returns

10
go.mod
View File

@@ -37,9 +37,6 @@ require (
github.com/pquerna/otp v1.5.0
github.com/rs/cors/wrapper/gin v0.0.0-20240830163046-1084d89a1692
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
github.com/twilio/twilio-go v1.28.3
github.com/urfave/cli-docs/v3 v3.0.0-alpha6
@@ -100,7 +97,6 @@ require (
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
@@ -125,7 +121,6 @@ require (
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
@@ -165,15 +160,11 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/cors v1.11.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/sosodev/duration v1.3.1 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/speakeasy-api/jsonpath v0.6.2 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
@@ -189,7 +180,6 @@ require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/net v0.44.0 // indirect

22
go.sum
View File

@@ -116,7 +116,6 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -157,8 +156,6 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
@@ -263,8 +260,6 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
@@ -403,8 +398,6 @@ github.com/rs/cors/wrapper/gin v0.0.0-20240830163046-1084d89a1692 h1:lwzJgPw5Y6p
github.com/rs/cors/wrapper/gin v0.0.0-20240830163046-1084d89a1692/go.mod h1:742Ialb8SOs5yB2PqRDzFcyND3280PoaS5/wcKQUQKE=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@@ -414,22 +407,11 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/speakeasy-api/jsonpath v0.6.2 h1:Mys71yd6u8kuowNCR0gCVPlVAHCmKtoGXYoAtcEbqXQ=
github.com/speakeasy-api/jsonpath v0.6.2/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -444,8 +426,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
@@ -502,8 +482,6 @@ go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mx
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=

View File

@@ -104,9 +104,11 @@ let
dir=$(realpath --relative-to="$PWD" "$absdir")
echo " Copying node_modules for $dir"
cp -r ${node_modules}/$dir/node_modules $dir/node_modules
done
pnpm audit-ci
echo " Running pnpm audit-ci for $dir"
pnpm audit-ci --directory $dir
done
${preCheck}

View File

@@ -28,7 +28,6 @@
"turbo": "2.3.3",
"typescript": "5.8.3",
"vite": "^5.4.20",
"vite-plugin-dts": "^3.9.1",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^0.32.4"
},

View File

@@ -103,7 +103,7 @@
"devDependencies": {
"@graphql-typed-document-node/core": "^3.2.0",
"@jest/globals": "^29.7.0",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-node-resolve": "^16.0.2",
"@types/jest": "^29.5.14",
"@types/node": "^22.15.17",
"@types/rollup-plugin-peer-deps-external": "^2.2.5",
@@ -118,5 +118,10 @@
"sideEffects": false,
"dependencies": {
"tslib": "^2.8.1"
},
"pnpm": {
"overrides": {
"rollup@<2.79.2": ">=2.79.2"
}
}
}

View File

@@ -4,6 +4,9 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
overrides:
rollup@<2.79.2: '>=2.79.2'
importers:
.:
@@ -19,8 +22,8 @@ importers:
specifier: ^29.7.0
version: 29.7.0
'@rollup/plugin-node-resolve':
specifier: ^16.0.1
version: 16.0.1(rollup@0.63.5)
specifier: ^16.0.2
version: 16.0.2(rollup@4.52.4)
'@types/jest':
specifier: ^29.5.14
version: 29.5.14
@@ -44,7 +47,7 @@ importers:
version: 3.6.2
rollup-plugin-peer-deps-external:
specifier: ^2.2.4
version: 2.2.4(rollup@0.63.5)
version: 2.2.4(rollup@4.52.4)
terser:
specifier: ^5.39.0
version: 5.39.0
@@ -318,24 +321,134 @@ packages:
'@jridgewell/trace-mapping@0.3.30':
resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==}
'@rollup/plugin-node-resolve@16.0.1':
resolution: {integrity: sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==}
'@rollup/plugin-node-resolve@16.0.2':
resolution: {integrity: sha512-tCtHJ2BlhSoK4cCs25NMXfV7EALKr0jyasmqVCq3y9cBrKdmJhtsy1iTz36Xhk/O+pDJbzawxF4K6ZblqCnITQ==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^2.78.0||^3.0.0||^4.0.0
rollup: '>=2.79.2'
peerDependenciesMeta:
rollup:
optional: true
'@rollup/pluginutils@5.2.0':
resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==}
'@rollup/pluginutils@5.3.0':
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
rollup: '>=2.79.2'
peerDependenciesMeta:
rollup:
optional: true
'@rollup/rollup-android-arm-eabi@4.52.4':
resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.52.4':
resolution: {integrity: sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.52.4':
resolution: {integrity: sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.52.4':
resolution: {integrity: sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-freebsd-arm64@4.52.4':
resolution: {integrity: sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==}
cpu: [arm64]
os: [freebsd]
'@rollup/rollup-freebsd-x64@4.52.4':
resolution: {integrity: sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==}
cpu: [x64]
os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.52.4':
resolution: {integrity: sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.52.4':
resolution: {integrity: sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.52.4':
resolution: {integrity: sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.52.4':
resolution: {integrity: sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-loong64-gnu@4.52.4':
resolution: {integrity: sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==}
cpu: [loong64]
os: [linux]
'@rollup/rollup-linux-ppc64-gnu@4.52.4':
resolution: {integrity: sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==}
cpu: [ppc64]
os: [linux]
'@rollup/rollup-linux-riscv64-gnu@4.52.4':
resolution: {integrity: sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-riscv64-musl@4.52.4':
resolution: {integrity: sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==}
cpu: [riscv64]
os: [linux]
'@rollup/rollup-linux-s390x-gnu@4.52.4':
resolution: {integrity: sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==}
cpu: [s390x]
os: [linux]
'@rollup/rollup-linux-x64-gnu@4.52.4':
resolution: {integrity: sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.52.4':
resolution: {integrity: sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==}
cpu: [x64]
os: [linux]
'@rollup/rollup-openharmony-arm64@4.52.4':
resolution: {integrity: sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==}
cpu: [arm64]
os: [openharmony]
'@rollup/rollup-win32-arm64-msvc@4.52.4':
resolution: {integrity: sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.52.4':
resolution: {integrity: sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-gnu@4.52.4':
resolution: {integrity: sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==}
cpu: [x64]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.52.4':
resolution: {integrity: sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==}
cpu: [x64]
os: [win32]
'@sinclair/typebox@0.27.8':
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
@@ -357,9 +470,6 @@ packages:
'@types/babel__traverse@7.28.0':
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
'@types/estree@0.0.39':
resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -1096,10 +1206,11 @@ packages:
rollup-plugin-peer-deps-external@2.2.4:
resolution: {integrity: sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g==}
peerDependencies:
rollup: '*'
rollup: '>=2.79.2'
rollup@0.63.5:
resolution: {integrity: sha512-dFf8LpUNzIj3oE0vCvobX6rqOzHzLBoblyFp+3znPbjiSmSvOoK2kMKx+Fv9jYduG1rvcCfCveSgEaQHjWRF6g==}
rollup@4.52.4:
resolution: {integrity: sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
semver@6.3.1:
@@ -1689,23 +1800,89 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@rollup/plugin-node-resolve@16.0.1(rollup@0.63.5)':
'@rollup/plugin-node-resolve@16.0.2(rollup@4.52.4)':
dependencies:
'@rollup/pluginutils': 5.2.0(rollup@0.63.5)
'@rollup/pluginutils': 5.3.0(rollup@4.52.4)
'@types/resolve': 1.20.2
deepmerge: 4.3.1
is-module: 1.0.0
resolve: 1.22.10
optionalDependencies:
rollup: 0.63.5
rollup: 4.52.4
'@rollup/pluginutils@5.2.0(rollup@0.63.5)':
'@rollup/pluginutils@5.3.0(rollup@4.52.4)':
dependencies:
'@types/estree': 1.0.8
estree-walker: 2.0.2
picomatch: 4.0.3
optionalDependencies:
rollup: 0.63.5
rollup: 4.52.4
'@rollup/rollup-android-arm-eabi@4.52.4':
optional: true
'@rollup/rollup-android-arm64@4.52.4':
optional: true
'@rollup/rollup-darwin-arm64@4.52.4':
optional: true
'@rollup/rollup-darwin-x64@4.52.4':
optional: true
'@rollup/rollup-freebsd-arm64@4.52.4':
optional: true
'@rollup/rollup-freebsd-x64@4.52.4':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.52.4':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.52.4':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.52.4':
optional: true
'@rollup/rollup-linux-arm64-musl@4.52.4':
optional: true
'@rollup/rollup-linux-loong64-gnu@4.52.4':
optional: true
'@rollup/rollup-linux-ppc64-gnu@4.52.4':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.52.4':
optional: true
'@rollup/rollup-linux-riscv64-musl@4.52.4':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.52.4':
optional: true
'@rollup/rollup-linux-x64-gnu@4.52.4':
optional: true
'@rollup/rollup-linux-x64-musl@4.52.4':
optional: true
'@rollup/rollup-openharmony-arm64@4.52.4':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.52.4':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.52.4':
optional: true
'@rollup/rollup-win32-x64-gnu@4.52.4':
optional: true
'@rollup/rollup-win32-x64-msvc@4.52.4':
optional: true
'@sinclair/typebox@0.27.8': {}
@@ -1738,8 +1915,6 @@ snapshots:
dependencies:
'@babel/types': 7.28.2
'@types/estree@0.0.39': {}
'@types/estree@1.0.8': {}
'@types/graceful-fs@4.1.9':
@@ -1769,7 +1944,7 @@ snapshots:
'@types/rollup-plugin-peer-deps-external@2.2.5':
dependencies:
rollup: 0.63.5
rollup: 4.52.4
'@types/stack-utils@2.0.3': {}
@@ -2614,14 +2789,37 @@ snapshots:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
rollup-plugin-peer-deps-external@2.2.4(rollup@0.63.5):
rollup-plugin-peer-deps-external@2.2.4(rollup@4.52.4):
dependencies:
rollup: 0.63.5
rollup: 4.52.4
rollup@0.63.5:
rollup@4.52.4:
dependencies:
'@types/estree': 0.0.39
'@types/node': 22.15.17
'@types/estree': 1.0.8
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.52.4
'@rollup/rollup-android-arm64': 4.52.4
'@rollup/rollup-darwin-arm64': 4.52.4
'@rollup/rollup-darwin-x64': 4.52.4
'@rollup/rollup-freebsd-arm64': 4.52.4
'@rollup/rollup-freebsd-x64': 4.52.4
'@rollup/rollup-linux-arm-gnueabihf': 4.52.4
'@rollup/rollup-linux-arm-musleabihf': 4.52.4
'@rollup/rollup-linux-arm64-gnu': 4.52.4
'@rollup/rollup-linux-arm64-musl': 4.52.4
'@rollup/rollup-linux-loong64-gnu': 4.52.4
'@rollup/rollup-linux-ppc64-gnu': 4.52.4
'@rollup/rollup-linux-riscv64-gnu': 4.52.4
'@rollup/rollup-linux-riscv64-musl': 4.52.4
'@rollup/rollup-linux-s390x-gnu': 4.52.4
'@rollup/rollup-linux-x64-gnu': 4.52.4
'@rollup/rollup-linux-x64-musl': 4.52.4
'@rollup/rollup-openharmony-arm64': 4.52.4
'@rollup/rollup-win32-arm64-msvc': 4.52.4
'@rollup/rollup-win32-ia32-msvc': 4.52.4
'@rollup/rollup-win32-x64-gnu': 4.52.4
'@rollup/rollup-win32-x64-msvc': 4.52.4
fsevents: 2.3.3
semver@6.3.1: {}

View File

@@ -1609,6 +1609,10 @@ export interface VerifyTicketParams {
export interface Client {
baseURL: string;
/** Add a middleware function to the fetch chain
* @param chainFunction - The middleware function to add
*/
pushChainFunction(chainFunction: ChainFunction): void;
/**
Summary: Get public keys for JWT verification in JWK Set format

View File

@@ -18,6 +18,11 @@ import {
export interface Client {
baseURL: string;
/** Add a middleware function to the fetch chain
* @param chainFunction - The middleware function to add
*/
pushChainFunction(chainFunction: ChainFunction): void;
/**
* Execute a request to a serverless function
* The response body will be automatically parsed based on the content type into the following types:
@@ -27,7 +32,8 @@ export interface Client {
*
* @param path - The path to the serverless function
* @param options - Additional fetch options to apply to the request
* @returns Promise with the function response and metadata. */
* @returns Promise with the function response and metadata.
*/
fetch<T = unknown>(
path: string,
options?: RequestInit,
@@ -69,7 +75,12 @@ export const createAPIClient = (
baseURL: string,
chainFunctions: ChainFunction[] = [],
): Client => {
const enhancedFetch = createEnhancedFetch(chainFunctions);
let enhancedFetch = createEnhancedFetch(chainFunctions);
const pushChainFunction = (chainFunction: ChainFunction) => {
chainFunctions.push(chainFunction);
enhancedFetch = createEnhancedFetch(chainFunctions);
};
/**
* Executes a request to a serverless function and processes the response
@@ -148,5 +159,6 @@ export const createAPIClient = (
baseURL,
fetch,
post,
pushChainFunction,
} as Client;
};

View File

@@ -91,6 +91,11 @@ export interface Client {
* URL for the GraphQL endpoint.
*/
url: string;
/** Add a middleware function to the fetch chain
* @param chainFunction - The middleware function to add
*/
pushChainFunction(chainFunction: ChainFunction): void;
}
/**
@@ -108,7 +113,12 @@ export const createAPIClient = (
url: string,
chainFunctions: ChainFunction[] = [],
): Client => {
const enhancedFetch = createEnhancedFetch(chainFunctions);
let enhancedFetch = createEnhancedFetch(chainFunctions);
const pushChainFunction = (chainFunction: ChainFunction) => {
chainFunctions.push(chainFunction);
enhancedFetch = createEnhancedFetch(chainFunctions);
};
const executeOperation = async <
TResponseData = unknown,
@@ -183,5 +193,6 @@ export const createAPIClient = (
return {
request,
url,
pushChainFunction,
} as Client;
};

View File

@@ -461,6 +461,10 @@ export interface GetFileMetadataHeadersParams {
export interface Client {
baseURL: string;
/** Add a middleware function to the fetch chain
* @param chainFunction - The middleware function to add
*/
pushChainFunction(chainFunction: ChainFunction): void;
/**
Summary: Upload files

600
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -67,14 +67,13 @@ func logFlags(ctx context.Context, logger *slog.Logger, cmd *cli.Command) {
flags := make([]any, 0, len(cmd.Root().Flags)+len(cmd.Flags))
for _, flag := range cmd.Root().Flags {
name := flag.Names()[0]
value := cmd.Generic(name)
value := cmd.Value(name)
var logValue any = value
if isSecret(name) {
logValue = "********"
value = "********"
}
flags = append(flags, slog.Any(name, logValue))
flags = append(flags, slog.Any(name, value))
processed[name] = struct{}{}
}

View File

@@ -1,36 +0,0 @@
package cmd
import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
func addBoolFlag(
flags *pflag.FlagSet,
name string,
defaultValue bool, //nolint:unparam
help string,
) {
flags.Bool(name, defaultValue, help)
if err := viper.BindPFlag(name, flags.Lookup(name)); err != nil {
cobra.CheckErr(err)
}
}
func addStringFlag(flags *pflag.FlagSet, name string, defaultValue string, help string) {
flags.String(name, defaultValue, help)
if err := viper.BindPFlag(name, flags.Lookup(name)); err != nil {
cobra.CheckErr(err)
}
}
func addStringArrayFlag(flags *pflag.FlagSet, name string, defaultValue []string, help string) {
flags.StringArray(name, defaultValue, help)
if err := viper.BindPFlag(name, flags.Lookup(name)); err != nil {
cobra.CheckErr(err)
}
}

View File

@@ -1,12 +1,103 @@
package cmd
import "github.com/sirupsen/logrus"
import (
"context"
"log/slog"
"os"
"strings"
"time"
func getLogger() *logrus.Logger {
logger := logrus.New()
logger.SetFormatter(&logrus.TextFormatter{ //nolint:exhaustruct
FullTimestamp: true,
})
"github.com/gin-gonic/gin"
"github.com/lmittmann/tint"
"github.com/urfave/cli/v3"
)
return logger
func getLogger(debug bool, formatTEXT bool) *slog.Logger {
var (
logLevel slog.Level
addSource bool
)
if debug {
logLevel = slog.LevelDebug
addSource = true
gin.SetMode(gin.DebugMode)
} else {
logLevel = slog.LevelInfo
addSource = false
gin.SetMode(gin.ReleaseMode)
}
var handler slog.Handler
if formatTEXT {
handler = tint.NewHandler(os.Stdout, &tint.Options{
AddSource: addSource,
Level: logLevel,
TimeFormat: time.StampMilli,
NoColor: false,
ReplaceAttr: nil,
})
} else {
handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: addSource,
Level: logLevel,
ReplaceAttr: nil,
})
}
return slog.New(handler)
}
func isSecret(name string) bool {
return strings.Contains(name, "pass") ||
strings.Contains(name, "token") ||
strings.Contains(name, "secret") ||
strings.Contains(name, "key") ||
strings.Contains(name, "license") ||
strings.Contains(name, "postgres") ||
strings.Contains(name, "client-id") ||
strings.Contains(name, "client-secret")
}
func logFlags(ctx context.Context, logger *slog.Logger, cmd *cli.Command) {
processed := make(map[string]struct{})
flags := make([]any, 0, len(cmd.Root().Flags)+len(cmd.Flags))
for _, flag := range cmd.Root().Flags {
name := flag.Names()[0]
value := cmd.Value(name)
if isSecret(name) {
value = "********"
}
flags = append(flags, slog.Any(name, value))
processed[name] = struct{}{}
}
for _, flag := range cmd.Flags {
name := flag.Names()[0]
if _, ok := processed[name]; ok {
continue
}
value := cmd.Generic(name)
var logValue any = value
if isSecret(name) {
logValue = "********"
}
flags = append(flags, slog.Any(name, logValue))
}
logger.LogAttrs(
ctx,
slog.LevelInfo,
"starting program",
slog.Group("flags", flags...),
)
}

View File

@@ -1,61 +0,0 @@
package cmd
import (
"fmt"
"os"
"strings"
"github.com/nhost/nhost/services/storage/controller"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const (
name = "storage"
debugFlag = "debug"
configFlag = "config"
)
var (
cfgFile *string
rootCmd = &cobra.Command{ //nolint:exhaustruct
Use: name,
Aliases: []string{},
SuggestFor: []string{},
Short: "Storage utilizes hasura and s3 to build a cloud storage",
Version: controller.Version(),
}
)
// Execute executes the root command.
func Execute() error {
return rootCmd.Execute() //nolint: wrapcheck
}
func init() {
cobra.OnInitialize(initConfig)
cfgFile = rootCmd.PersistentFlags().StringP(configFlag, "c", "", "use this configuration file")
addBoolFlag(rootCmd.PersistentFlags(), debugFlag, false, "enable debug messages")
}
func initConfig() {
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
if *cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(*cfgFile)
} else {
// Find home directory.
viper.SetConfigName(name)
viper.AddConfigPath("$HOME")
viper.AddConfigPath(".")
}
if err := viper.ReadInConfig(); err != nil {
fmt.Fprintf(os.Stderr, "problem reading %s: %s\n", viper.ConfigFileUsed(), err)
} else {
fmt.Fprintln(os.Stderr, "using config file:", viper.ConfigFileUsed())
}
}

View File

@@ -3,8 +3,8 @@ package cmd
import (
"context"
"fmt"
"log/slog"
"net/http"
"os"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
@@ -24,33 +24,33 @@ import (
"github.com/nhost/nhost/services/storage/migrations"
"github.com/nhost/nhost/services/storage/storage"
ginmiddleware "github.com/oapi-codegen/gin-middleware"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/urfave/cli/v3"
)
const (
publicURLFlag = "public-url"
apiRootPrefixFlag = "api-root-prefix"
bindFlag = "bind"
hasuraEndpointFlag = "hasura-endpoint"
hasuraMetadataFlag = "hasura-metadata"
hasuraAdminSecretFlag = "hasura-graphql-admin-secret" //nolint: gosec
s3EndpointFlag = "s3-endpoint"
s3AccessKeyFlag = "s3-access-key"
s3SecretKeyFlag = "s3-secret-key" //nolint: gosec
s3RegionFlag = "s3-region"
s3BucketFlag = "s3-bucket"
s3RootFolderFlag = "s3-root-folder"
s3DisableHTTPS = "s3-disable-http"
postgresMigrationsFlag = "postgres-migrations"
postgresMigrationsSourceFlag = "postgres-migrations-source"
fastlyServiceFlag = "fastly-service"
fastlyKeyFlag = "fastly-key"
corsAllowOriginsFlag = "cors-allow-origins"
corsAllowCredentialsFlag = "cors-allow-credentials" //nolint: gosec
clamavServerFlag = "clamav-server"
hasuraDBNameFlag = "hasura-db-name"
flagDebug = "debug"
flagLogFormatTEXT = "log-format-text"
flagPublicURL = "public-url"
flagAPIRootPrefix = "api-root-prefix"
flagBind = "bind"
flagHasuraEndpoint = "hasura-endpoint"
flagHasuraMetadata = "hasura-metadata"
flagHasuraAdminSecret = "hasura-graphql-admin-secret" //nolint: gosec
flagS3Endpoint = "s3-endpoint"
flagS3AccessKey = "s3-access-key"
flagS3SecretKey = "s3-secret-key" //nolint: gosec
flagS3Region = "s3-region"
flagS3Bucket = "s3-bucket"
flagS3RootFolder = "s3-root-folder"
flagS3DisableHTTPS = "s3-disable-https"
flagPostgresMigrations = "postgres-migrations"
flagPostgresMigrationsSource = "postgres-migrations-source"
flagFastlyService = "fastly-service"
flagFastlyKey = "fastly-key"
flagCorsAllowOrigins = "cors-allow-origins"
flagCorsAllowCredentials = "cors-allow-credentials" //nolint: gosec
flagClamavServer = "clamav-server"
flagHasuraDBName = "hasura-db-name"
)
func getCorsMiddleware(
@@ -81,10 +81,13 @@ func getGin( //nolint:funlen
metadataStorage controller.MetadataStorage,
contentStorage controller.ContentStorage,
imageTransformer *image.Transformer,
logger *logrus.Logger,
logger *slog.Logger,
debug bool,
corsAllowOrigins []string,
corsAllowCredentials bool,
fastlyService string,
fastlyKey string,
clamavServer string,
) (*http.Server, error) {
router := gin.New()
@@ -113,18 +116,17 @@ func getGin( //nolint:funlen
gin.Recovery(),
}
fastlyService := viper.GetString(fastlyServiceFlag)
if fastlyService != "" {
logger.Info("enabling fastly middleware")
logger.InfoContext(context.Background(), "enabling fastly middleware")
handlers = append(
handlers,
fastly.New(fastlyService, viper.GetString(fastlyKeyFlag), logger),
fastly.New(fastlyService, fastlyKey, logger),
)
}
router.Use(handlers...)
av, err := getAv(viper.GetString(clamavServerFlag))
av, err := getAv(clamavServer)
if err != nil {
return nil, fmt.Errorf("problem trying to get av: %w", err)
}
@@ -177,15 +179,19 @@ func getContentStorage(
ctx context.Context,
s3Endpoint, region, s3AccessKey, s3SecretKey, bucket, rootFolder string,
disableHTTPS bool,
logger *logrus.Logger,
logger *slog.Logger,
) *storage.S3 {
var (
cfg aws.Config
err error
)
if region == "" {
region = "no-region"
}
if s3AccessKey != "" && s3SecretKey != "" {
logger.Info("Using static aws credentials")
logger.InfoContext(ctx, "Using static aws credentials")
cfg, err = config.LoadDefaultConfig(
ctx,
@@ -195,7 +201,7 @@ func getContentStorage(
),
)
} else {
logger.Info("Using default configuration for aws credentials")
logger.InfoContext(ctx, "Using default configuration for aws credentials")
cfg, err = config.LoadDefaultConfig(ctx, config.WithRegion(region))
}
@@ -217,211 +223,273 @@ func getContentStorage(
return st
}
func applymigrations(
func applyMigrations(
ctx context.Context,
postgresMigrations bool,
postgresSource string,
hasuraMetadata bool,
hasuraEndpoint string,
hasuraSecret string,
hasuraDBName string,
logger *logrus.Logger,
) {
logger *slog.Logger,
) error {
if postgresMigrations {
if postgresSource == "" {
logger.Error("you need to specify " + postgresMigrationsSourceFlag)
os.Exit(1)
}
logger.Info("applying postgres migrations")
logger.InfoContext(ctx, "applying postgres migrations")
if err := migrations.ApplyPostgresMigration(postgresSource); err != nil {
logger.Errorf("problem applying postgres migrations: %s", err.Error())
os.Exit(1)
return fmt.Errorf("problem applying postgres migrations: %w", err)
}
}
if hasuraMetadata {
logger.Info("applying hasura metadata")
logger.InfoContext(ctx, "applying hasura metadata")
if err := migrations.ApplyHasuraMetadata(hasuraEndpoint, hasuraSecret, hasuraDBName); err != nil {
logger.Errorf("problem applying hasura metadata: %s", err.Error())
os.Exit(1)
if err := migrations.ApplyHasuraMetadata(
ctx, hasuraEndpoint, hasuraSecret, hasuraDBName,
); err != nil {
return fmt.Errorf("problem applying hasura metadata: %w", err)
}
}
return nil
}
func init() { //nolint:funlen
rootCmd.AddCommand(serveCmd)
{
addStringFlag(
serveCmd.Flags(),
publicURLFlag,
"http://localhost:8000",
"public URL of the service",
)
addStringFlag(serveCmd.Flags(), apiRootPrefixFlag, "/v1", "API root prefix")
addStringFlag(serveCmd.Flags(), bindFlag, ":8000", "bind the service to this address")
}
{
addStringFlag(
serveCmd.Flags(),
hasuraEndpointFlag,
"",
"Use this endpoint when connecting using graphql as metadata storage",
)
}
{
addStringFlag(serveCmd.Flags(), s3EndpointFlag, "", "S3 Endpoint")
addStringFlag(serveCmd.Flags(), s3AccessKeyFlag, "", "S3 Access key")
addStringFlag(serveCmd.Flags(), s3SecretKeyFlag, "", "S3 Secret key")
addStringFlag(serveCmd.Flags(), s3RegionFlag, "no-region", "S3 region")
addStringFlag(serveCmd.Flags(), s3BucketFlag, "", "S3 bucket")
addStringFlag(
serveCmd.Flags(),
s3RootFolderFlag,
"",
"All buckets will be created inside this root",
)
}
{
addBoolFlag(serveCmd.Flags(), postgresMigrationsFlag, false, "Apply Postgres migrations")
addStringFlag(
serveCmd.Flags(),
postgresMigrationsSourceFlag,
"",
"postgres connection, i.e. postgres://user@pass:localhost:5432/mydb",
)
addStringFlag(serveCmd.Flags(), hasuraDBNameFlag, "default", "Hasura database name")
}
{
addBoolFlag(serveCmd.Flags(), hasuraMetadataFlag, false, "Apply Hasura's metadata")
addStringFlag(serveCmd.Flags(), hasuraAdminSecretFlag, "", "")
}
{
addStringFlag(
serveCmd.Flags(),
fastlyServiceFlag,
"",
"Enable Fastly middleware and enable automated purges",
)
addStringFlag(serveCmd.Flags(), fastlyKeyFlag, "", "Fastly CDN Key to authenticate purges")
}
{
addStringArrayFlag(
serveCmd.Flags(),
corsAllowOriginsFlag,
[]string{"*"},
"CORS allow origins",
)
addBoolFlag(serveCmd.Flags(), corsAllowCredentialsFlag, false, "CORS allow credentials")
addStringFlag(
serveCmd.Flags(),
clamavServerFlag,
"",
"If set, use ClamAV to scan files. Example: tcp://clamavd:3310",
)
func CommandServe() *cli.Command { //nolint:funlen
return &cli.Command{ //nolint:exhaustruct
Name: "serve",
Usage: "Start storage server",
Flags: []cli.Flag{
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagDebug,
Usage: "enable debug messages",
Category: "general",
Sources: cli.EnvVars("DEBUG"),
},
&cli.BoolFlag{ //nolint: exhaustruct
Name: flagLogFormatTEXT,
Usage: "format logs in plain text",
Category: "general",
Value: false,
Sources: cli.EnvVars("LOG_FORMAT_TEXT"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagPublicURL,
Usage: "public URL of the service",
Value: "http://localhost:8000",
Category: "server",
Sources: cli.EnvVars("PUBLIC_URL"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagAPIRootPrefix,
Usage: "API root prefix",
Value: "/v1",
Category: "server",
Sources: cli.EnvVars("API_ROOT_PREFIX"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagBind,
Usage: "bind the service to this address",
Value: ":8000",
Category: "server",
Sources: cli.EnvVars("BIND"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagHasuraEndpoint,
Usage: "Use this endpoint when connecting using graphql as metadata storage",
Category: "hasura",
Sources: cli.EnvVars("HASURA_ENDPOINT"),
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagHasuraMetadata,
Usage: "Apply Hasura's metadata",
Category: "hasura",
Sources: cli.EnvVars("HASURA_METADATA"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagHasuraAdminSecret,
Usage: "Hasura admin secret",
Category: "hasura",
Sources: cli.EnvVars(
"HASURA_GRAPHQL_ADMIN_SECRET",
"HASURA_GRAPHQL_ADMIN_SECRET",
),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagHasuraDBName,
Usage: "Hasura database name",
Value: "default",
Category: "hasura",
Sources: cli.EnvVars("HASURA_DB_NAME"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagS3Endpoint,
Usage: "S3 Endpoint",
Category: "s3",
Sources: cli.EnvVars("S3_ENDPOINT"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagS3AccessKey,
Usage: "S3 Access key",
Category: "s3",
Sources: cli.EnvVars("S3_ACCESS_KEY"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagS3SecretKey,
Usage: "S3 Secret key",
Category: "s3",
Sources: cli.EnvVars("S3_SECRET_KEY"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagS3Region,
Usage: "S3 region",
Value: "no-region",
Category: "s3",
Sources: cli.EnvVars("S3_REGION"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagS3Bucket,
Usage: "S3 bucket",
Category: "s3",
Sources: cli.EnvVars("S3_BUCKET"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagS3RootFolder,
Usage: "All buckets will be created inside this root",
Category: "s3",
Sources: cli.EnvVars("S3_ROOT_FOLDER"),
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagS3DisableHTTPS,
Usage: "Disable HTTPS for S3",
Category: "s3",
Sources: cli.EnvVars("S3_DISABLE_HTTPS"),
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagPostgresMigrations,
Usage: "Apply Postgres migrations",
Category: "postgres",
Sources: cli.EnvVars("POSTGRES_MIGRATIONS"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagPostgresMigrationsSource,
Usage: "postgres connection, i.e. postgres://user@pass:localhost:5432/mydb",
Category: "postgres",
Required: true,
Sources: cli.EnvVars("POSTGRES_MIGRATIONS_SOURCE"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagFastlyService,
Usage: "Enable Fastly middleware and enable automated purges",
Category: "cdn",
Sources: cli.EnvVars("FASTLY_SERVICE"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagFastlyKey,
Usage: "Fastly CDN Key to authenticate purges",
Category: "cdn",
Sources: cli.EnvVars("FASTLY_KEY"),
},
&cli.StringSliceFlag{ //nolint:exhaustruct
Name: flagCorsAllowOrigins,
Usage: "CORS allow origins",
Value: []string{"*"},
Category: "cors",
Sources: cli.EnvVars("CORS_ALLOW_ORIGINS"),
},
&cli.BoolFlag{ //nolint:exhaustruct
Name: flagCorsAllowCredentials,
Usage: "CORS allow credentials",
Category: "cors",
Sources: cli.EnvVars("CORS_ALLOW_CREDENTIALS"),
},
&cli.StringFlag{ //nolint:exhaustruct
Name: flagClamavServer,
Usage: "If set, use ClamAV to scan files. Example: tcp://clamavd:3310",
Category: "antivirus",
Sources: cli.EnvVars("CLAMAV_SERVER"),
},
},
Action: serve,
}
}
var serveCmd = &cobra.Command{ //nolint:exhaustruct
Use: "serve",
Short: "Starts storage server",
Run: func(cmd *cobra.Command, _ []string) {
logger := getLogger()
func serve(ctx context.Context, cmd *cli.Command) error { //nolint:funlen
logger := getLogger(cmd.Bool(flagDebug), cmd.Bool(flagLogFormatTEXT))
logger.InfoContext(ctx, cmd.Root().Name+" v"+cmd.Root().Version)
logFlags(ctx, logger, cmd)
logger.Info("storage version ", controller.Version())
imageTransformer := image.NewTransformer()
defer imageTransformer.Shutdown()
ctx, cancel := context.WithCancel(cmd.Context())
servCtx, cancel := context.WithCancel(ctx)
defer cancel()
contentStorage := getContentStorage(
servCtx,
cmd.String(flagS3Endpoint),
cmd.String(flagS3Region),
cmd.String(flagS3AccessKey),
cmd.String(flagS3SecretKey),
cmd.String(flagS3Bucket),
cmd.String(flagS3RootFolder),
cmd.Bool(flagS3DisableHTTPS),
logger,
)
if err := applyMigrations(
ctx,
cmd.Bool(flagPostgresMigrations),
cmd.String(flagPostgresMigrationsSource),
cmd.Bool(flagHasuraMetadata),
cmd.String(flagHasuraEndpoint),
cmd.String(flagHasuraAdminSecret),
cmd.String(flagHasuraDBName),
logger,
); err != nil {
return err
}
metadataStorage := getMetadataStorage(
cmd.String(flagHasuraEndpoint) + "/graphql",
)
server, err := getGin( //nolint: contextcheck
cmd.String(flagBind),
cmd.String(flagPublicURL),
cmd.String(flagAPIRootPrefix),
cmd.String(flagHasuraAdminSecret),
metadataStorage,
contentStorage,
imageTransformer,
logger,
cmd.Bool(flagDebug),
cmd.StringSlice(flagCorsAllowOrigins),
cmd.Bool(flagCorsAllowCredentials),
cmd.String(flagFastlyService),
cmd.String(flagFastlyKey),
cmd.String(flagClamavServer),
)
if err != nil {
return err
}
go func() {
defer cancel()
if viper.GetBool(debugFlag) {
logger.SetLevel(logrus.DebugLevel)
gin.SetMode(gin.DebugMode)
} else {
logger.SetLevel(logrus.InfoLevel)
gin.SetMode(gin.ReleaseMode)
logger.InfoContext(servCtx, "starting server")
if err := server.ListenAndServe(); err != nil {
logger.ErrorContext(servCtx, "server failed", slog.String("error", err.Error()))
}
}()
imageTransformer := image.NewTransformer()
defer imageTransformer.Shutdown()
<-servCtx.Done()
logger.WithFields(
logrus.Fields{
debugFlag: viper.GetBool(debugFlag),
bindFlag: viper.GetString(bindFlag),
hasuraEndpointFlag: viper.GetString(hasuraEndpointFlag),
postgresMigrationsFlag: viper.GetBool(postgresMigrationsFlag),
hasuraMetadataFlag: viper.GetBool(hasuraMetadataFlag),
s3EndpointFlag: viper.GetString(s3EndpointFlag),
s3RegionFlag: viper.GetString(s3RegionFlag),
s3BucketFlag: viper.GetString(s3BucketFlag),
s3RootFolderFlag: viper.GetString(s3RootFolderFlag),
clamavServerFlag: viper.GetString(clamavServerFlag),
hasuraDBNameFlag: viper.GetString(hasuraDBNameFlag),
},
).Debug("parameters")
logger.InfoContext(ctx, "shutting down server")
contentStorage := getContentStorage(
ctx,
viper.GetString(s3EndpointFlag),
viper.GetString(s3RegionFlag),
viper.GetString(s3AccessKeyFlag),
viper.GetString(s3SecretKeyFlag),
viper.GetString(s3BucketFlag),
viper.GetString(s3RootFolderFlag),
viper.GetBool(s3DisableHTTPS),
logger,
)
if err := server.Shutdown(ctx); err != nil {
logger.ErrorContext(ctx, "problem shutting down server", slog.String("error", err.Error()))
}
applymigrations(
viper.GetBool(postgresMigrationsFlag),
viper.GetString(postgresMigrationsSourceFlag),
viper.GetBool(hasuraMetadataFlag),
viper.GetString(hasuraEndpointFlag),
viper.GetString(hasuraAdminSecretFlag),
viper.GetString(hasuraDBNameFlag),
logger,
)
metadataStorage := getMetadataStorage(
viper.GetString(hasuraEndpointFlag) + "/graphql",
)
server, err := getGin(
viper.GetString(bindFlag),
viper.GetString(publicURLFlag),
viper.GetString(apiRootPrefixFlag),
viper.GetString(hasuraAdminSecretFlag),
metadataStorage,
contentStorage,
imageTransformer,
logger,
viper.GetBool(debugFlag),
viper.GetStringSlice(corsAllowOriginsFlag),
viper.GetBool(corsAllowCredentialsFlag),
)
cobra.CheckErr(err)
go func() {
defer cancel()
logger.Info("starting server")
if err := server.ListenAndServe(); err != nil {
logger.Error("server failed", logrus.Fields{
"error": err,
})
}
}()
<-ctx.Done()
logger.Info("shutting down server")
err = server.Shutdown(ctx)
cobra.CheckErr(err)
},
return nil
}

View File

@@ -4,12 +4,12 @@ package controller
import (
"context"
"io"
"log/slog"
"net/http"
"time"
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/image"
"github.com/sirupsen/logrus"
)
type FileSummary struct {
@@ -91,7 +91,7 @@ type Controller struct {
contentStorage ContentStorage
imageTransformer *image.Transformer
av Antivirus
logger *logrus.Logger
logger *slog.Logger
}
func New(
@@ -102,7 +102,7 @@ func New(
contentStorage ContentStorage,
imageTransformer *image.Transformer,
av Antivirus,
logger *logrus.Logger,
logger *slog.Logger,
) *Controller {
return &Controller{
publicURL,

View File

@@ -2,6 +2,7 @@ package controller
import (
"context"
"log/slog"
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/middleware"
@@ -32,7 +33,10 @@ func (ctrl *Controller) DeleteBrokenMetadata( //nolint:ireturn
files, apiErr := ctrl.deleteBrokenMetadata(ctx)
if apiErr != nil {
logger.WithError(apiErr).Error("failed to delete broken metadata")
logger.ErrorContext(
ctx, "failed to delete broken metadata", slog.String("error", apiErr.Error()),
)
return apiErr, nil
}

View File

@@ -1,12 +1,13 @@
package controller_test
import (
"log/slog"
"os"
"testing"
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/controller"
"github.com/nhost/nhost/services/storage/controller/mock"
"github.com/sirupsen/logrus"
gomock "go.uber.org/mock/gomock"
)
@@ -43,8 +44,9 @@ func TestDeleteBrokenMetadata(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
logger := logrus.New()
logger.SetLevel(logrus.ErrorLevel)
logger := slog.New(
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}),
)
c := gomock.NewController(t)
defer c.Finish()

View File

@@ -2,6 +2,7 @@ package controller
import (
"context"
"log/slog"
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/middleware"
@@ -17,12 +18,18 @@ func (ctrl *Controller) DeleteFile( //nolint:ireturn
apiErr := ctrl.metadataStorage.DeleteFileByID(ctx, request.Id, sessionHeaders)
if apiErr != nil {
logger.WithError(apiErr).Error("problem deleting file metadata")
logger.ErrorContext(
ctx, "problem deleting file metadata", slog.String("error", apiErr.Error()),
)
return apiErr, nil
}
if apiErr := ctrl.contentStorage.DeleteFile(ctx, request.Id); apiErr != nil {
logger.WithError(apiErr).Error("problem deleting file content")
logger.ErrorContext(
ctx, "problem deleting file content", slog.String("error", apiErr.Error()),
)
return apiErr, nil
}

View File

@@ -1,12 +1,13 @@
package controller_test
import (
"log/slog"
"os"
"testing"
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/controller"
"github.com/nhost/nhost/services/storage/controller/mock"
"github.com/sirupsen/logrus"
gomock "go.uber.org/mock/gomock"
)
@@ -23,8 +24,7 @@ func TestDeleteFile(t *testing.T) {
},
}
logger := logrus.New()
logger.SetLevel(logrus.ErrorLevel)
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {

View File

@@ -2,6 +2,7 @@ package controller
import (
"context"
"log/slog"
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/middleware"
@@ -30,7 +31,10 @@ func (ctrl *Controller) DeleteOrphanedFiles( //nolint:ireturn
files, apiErr := ctrl.deleteOrphans(ctx)
if apiErr != nil {
logger.WithError(apiErr).Error("failed to delete orphaned files")
logger.ErrorContext(
ctx, "failed to delete orphaned files", slog.String("error", apiErr.Error()),
)
return apiErr, nil
}

View File

@@ -1,12 +1,13 @@
package controller_test
import (
"log/slog"
"os"
"testing"
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/controller"
"github.com/nhost/nhost/services/storage/controller/mock"
"github.com/sirupsen/logrus"
gomock "go.uber.org/mock/gomock"
)
@@ -29,8 +30,9 @@ func TestDeleteOrphans(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
logger := logrus.New()
logger.SetLevel(logrus.ErrorLevel)
logger := slog.New(
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}),
)
c := gomock.NewController(t)
defer c.Finish()

View File

@@ -5,6 +5,7 @@ import (
"context"
"fmt"
"io"
"log/slog"
"net/http"
"net/url"
"slices"
@@ -14,7 +15,6 @@ import (
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/image"
"github.com/nhost/nhost/services/storage/middleware"
"github.com/sirupsen/logrus"
)
func deptr[T any](v *T) T { //nolint:ireturn
@@ -254,8 +254,9 @@ func (ctrl *Controller) processFileToDownload(
}
func (ctrl *Controller) getFileResponse( //nolint: ireturn,dupl
ctx context.Context,
file *processedFile,
logger logrus.FieldLogger,
logger *slog.Logger,
) api.GetFileResponseObject {
switch file.statusCode {
case http.StatusOK:
@@ -311,8 +312,9 @@ func (ctrl *Controller) getFileResponse( //nolint: ireturn,dupl
},
}
default:
logger.WithField("statusCode", file.statusCode).
Error("unexpected status code from download")
logger.ErrorContext(
ctx, "unexpected status code from download", slog.Int("statusCode", file.statusCode),
)
return ErrUnexpectedStatusCode
}
@@ -330,7 +332,10 @@ func (ctrl *Controller) GetFile( //nolint:ireturn
ctx, request.Id, true, sessionHeaders,
)
if apiErr != nil {
logger.WithError(apiErr).Error("failed to get file metadata")
logger.ErrorContext(
ctx, "failed to get file metadata", slog.String("error", apiErr.Error()),
)
return apiErr, nil
}
@@ -350,9 +355,12 @@ func (ctrl *Controller) GetFile( //nolint:ireturn
acceptHeader,
)
if apiErr != nil {
logger.WithError(apiErr).Error("failed to process file for download")
logger.ErrorContext(
ctx, "failed to process file for download", slog.String("error", apiErr.Error()),
)
return apiErr, nil
}
return ctrl.getFileResponse(processedFile, logger), nil
return ctrl.getFileResponse(ctx, processedFile, logger), nil
}

View File

@@ -1,6 +1,8 @@
package controller_test
import (
"log/slog"
"os"
"testing"
"time"
@@ -8,7 +10,6 @@ import (
"github.com/nhost/nhost/services/storage/controller"
"github.com/nhost/nhost/services/storage/controller/mock"
"github.com/nhost/nhost/services/storage/image"
"github.com/sirupsen/logrus"
gomock "go.uber.org/mock/gomock"
)
@@ -194,8 +195,7 @@ func TestGetFileInfo(t *testing.T) {
},
}
logger := logrus.New()
logger.SetLevel(logrus.ErrorLevel)
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"log/slog"
"time"
"github.com/nhost/nhost/services/storage/api"
@@ -24,13 +25,18 @@ func (ctrl *Controller) GetFilePresignedURL( //nolint:ireturn
ctx context.Context, request api.GetFilePresignedURLRequestObject,
) (api.GetFilePresignedURLResponseObject, error) {
logger := middleware.LoggerFromContext(ctx)
logger = logger.With("file_id", request.Id)
sessionHeaders := middleware.SessionHeadersFromContext(ctx)
fileMetadata, bucketMetadata, apiErr := ctrl.getFileMetadata(
ctx, request.Id, true, sessionHeaders,
)
if apiErr != nil {
logger.WithError(apiErr).Error("error getting file metadata")
logger.ErrorContext(
ctx, "error getting file metadata", slog.String("error", apiErr.Error()),
)
return apiErr, nil
}
@@ -38,7 +44,8 @@ func (ctrl *Controller) GetFilePresignedURL( //nolint:ireturn
err := errors.New( //nolint: err113
"presigned URLs are not enabled on the bucket where this file is located in",
)
logger.WithError(err).Error("presigned URLs not enabled for bucket")
logger.ErrorContext(ctx, "presigned URLs not enabled for bucket")
return ForbiddenError(err, err.Error()), nil
}
@@ -49,7 +56,9 @@ func (ctrl *Controller) GetFilePresignedURL( //nolint:ireturn
time.Duration(bucketMetadata.DownloadExpiration)*time.Second,
)
if apiErr != nil {
logger.WithError(apiErr).Error("error creating presigned URL for file")
logger.ErrorContext(
ctx, "error creating presigned URL for file", slog.String("error", apiErr.Error()))
return apiErr, nil
}

View File

@@ -2,13 +2,14 @@ package controller_test
import (
"fmt"
"log/slog"
"os"
"testing"
"time"
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/controller"
"github.com/nhost/nhost/services/storage/controller/mock"
"github.com/sirupsen/logrus"
gomock "go.uber.org/mock/gomock"
)
@@ -32,8 +33,7 @@ func TestGetFilePresignedURL(t *testing.T) {
},
}
logger := logrus.New()
logger.SetLevel(logrus.ErrorLevel)
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {

View File

@@ -2,7 +2,9 @@ package controller_test
import (
"io"
"log/slog"
"net/http"
"os"
"strings"
"testing"
"time"
@@ -10,7 +12,6 @@ import (
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/controller"
"github.com/nhost/nhost/services/storage/controller/mock"
"github.com/sirupsen/logrus"
gomock "go.uber.org/mock/gomock"
)
@@ -200,8 +201,7 @@ func TestGetFile(t *testing.T) { //nolint:maintidx
},
}
logger := logrus.New()
logger.SetLevel(logrus.ErrorLevel)
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"net/url"
"strconv"
@@ -12,7 +13,6 @@ import (
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/middleware"
"github.com/sirupsen/logrus"
)
const (
@@ -81,8 +81,9 @@ func getAmazonSignature(request api.GetFileWithPresignedURLRequestObject) string
}
func (ctrl *Controller) getFileWithPresignedURLResponseObject( //nolint: ireturn,dupl
ctx context.Context,
file *processedFile,
logger logrus.FieldLogger,
logger *slog.Logger,
) api.GetFileWithPresignedURLResponseObject {
switch file.statusCode {
case http.StatusOK:
@@ -138,8 +139,9 @@ func (ctrl *Controller) getFileWithPresignedURLResponseObject( //nolint: ireturn
},
}
default:
logger.WithField("statusCode", file.statusCode).
Error("unexpected status code from download")
logger.ErrorContext(
ctx, "unexpected status code from download", slog.Int("statusCode", file.statusCode),
)
return ErrUnexpectedStatusCode
}
@@ -159,7 +161,10 @@ func (ctrl *Controller) GetFileWithPresignedURL( //nolint: ireturn
http.Header{"x-hasura-admin-secret": []string{ctrl.hasuraAdminSecret}},
)
if apiErr != nil {
logger.WithError(apiErr).Error("failed to get file metadata")
logger.ErrorContext(
ctx, "failed to get file metadata", slog.String("error", apiErr.Error()),
)
return apiErr, nil
}
@@ -172,7 +177,10 @@ func (ctrl *Controller) GetFileWithPresignedURL( //nolint: ireturn
expires, apiErr := expiresIn(request.Params.XAmzExpires, request.Params.XAmzDate)
if apiErr != nil {
logger.WithError(apiErr).Error("failed to parse expiration time")
logger.ErrorContext(
ctx, "failed to parse expiration time", slog.String("error", apiErr.Error()),
)
return apiErr, nil
}
@@ -193,9 +201,12 @@ func (ctrl *Controller) GetFileWithPresignedURL( //nolint: ireturn
acceptHeader,
)
if apiErr != nil {
logger.WithError(apiErr).Error("failed to process file for download")
logger.ErrorContext(
ctx, "failed to process file for download", slog.String("error", apiErr.Error()),
)
return nil, apiErr
}
return ctrl.getFileWithPresignedURLResponseObject(processedFile, logger), nil
return ctrl.getFileWithPresignedURLResponseObject(ctx, processedFile, logger), nil
}

View File

@@ -2,6 +2,7 @@ package controller
import (
"context"
"log/slog"
"path"
"github.com/nhost/nhost/services/storage/api"
@@ -63,7 +64,10 @@ func (ctrl *Controller) ListBrokenMetadata( //nolint:ireturn
files, apiErr := ctrl.listBrokenMetadata(ctx)
if apiErr != nil {
logger.WithError(apiErr).Error("failed to list broken metadata")
logger.ErrorContext(
ctx, "failed to list broken metadata", slog.String("error", apiErr.Error()),
)
return apiErr, nil
}

View File

@@ -1,12 +1,13 @@
package controller_test
import (
"log/slog"
"os"
"testing"
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/controller"
"github.com/nhost/nhost/services/storage/controller/mock"
"github.com/sirupsen/logrus"
gomock "go.uber.org/mock/gomock"
)
@@ -42,8 +43,9 @@ func TestListBrokenMetadata(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
logger := logrus.New()
logger.SetLevel(logrus.ErrorLevel)
logger := slog.New(
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}),
)
c := gomock.NewController(t)
defer c.Finish()

View File

@@ -3,6 +3,7 @@ package controller
import (
"context"
"fmt"
"log/slog"
"net/http"
"github.com/gin-gonic/gin"
@@ -55,7 +56,10 @@ func (ctrl *Controller) ListFilesNotUploaded( //nolint:ireturn
files, apiErr := ctrl.listNotUploaded(ctx)
if apiErr != nil {
logger.WithError(apiErr).Error("failed to list not uploaded files")
logger.ErrorContext(
ctx, "failed to list not uploaded files", slog.String("error", apiErr.Error()),
)
return apiErr, nil
}

View File

@@ -1,12 +1,13 @@
package controller_test
import (
"log/slog"
"os"
"testing"
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/controller"
"github.com/nhost/nhost/services/storage/controller/mock"
"github.com/sirupsen/logrus"
gomock "go.uber.org/mock/gomock"
)
@@ -36,8 +37,9 @@ func TestListNotUploaded(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
logger := logrus.New()
logger.SetLevel(logrus.ErrorLevel)
logger := slog.New(
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}),
)
c := gomock.NewController(t)
defer c.Finish()

View File

@@ -2,6 +2,7 @@ package controller
import (
"context"
"log/slog"
"net/http"
"path"
@@ -50,7 +51,10 @@ func (ctrl *Controller) ListOrphanedFiles( //nolint:ireturn
files, apiErr := ctrl.listOrphans(ctx)
if apiErr != nil {
logger.WithError(apiErr).Error("failed to delete orphaned files")
logger.ErrorContext(
ctx, "failed to list orphaned files", slog.String("error", apiErr.Error()),
)
return apiErr, nil
}

View File

@@ -1,12 +1,13 @@
package controller_test
import (
"log/slog"
"os"
"testing"
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/controller"
"github.com/nhost/nhost/services/storage/controller/mock"
"github.com/sirupsen/logrus"
gomock "go.uber.org/mock/gomock"
)
@@ -29,8 +30,9 @@ func TestListOrphans(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
logger := logrus.New()
logger.SetLevel(logrus.ErrorLevel)
logger := slog.New(
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}),
)
c := gomock.NewController(t)
defer c.Finish()

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"log/slog"
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/middleware"
@@ -71,7 +72,7 @@ func (ctrl *Controller) ReplaceFile( //nolint:funlen,ireturn
file, apiErr := replaceFileParseRequest(request)
if apiErr != nil {
logger.WithError(apiErr).Error("problem parsing request")
logger.ErrorContext(ctx, "problem parsing request", slog.String("error", apiErr.Error()))
return apiErr, nil
}
@@ -79,22 +80,34 @@ func (ctrl *Controller) ReplaceFile( //nolint:funlen,ireturn
ctx, request.Id, false, sessionHeaders,
)
if apiErr != nil {
logger.WithError(apiErr).Error("problem getting file metadata")
logger.ErrorContext(
ctx, "problem getting file metadata", slog.String("error", apiErr.Error()),
)
return apiErr, nil
}
if apiErr = checkFileSize(
file.header, bucketMetadata.MinUploadFile, bucketMetadata.MaxUploadFile,
); apiErr != nil {
apiErr := fmt.Errorf("problem checking file size %s: %w", file.Name, apiErr)
logger.WithError(apiErr).Errorf("problem checking file size %s", file.Name)
wrappedErr := fmt.Errorf("problem checking file size %s: %w", file.Name, apiErr)
logger.ErrorContext(
ctx, "problem checking file size",
slog.String("error", wrappedErr.Error()),
slog.String("fileName", file.Name),
)
return InternalServerError(apiErr), nil
return InternalServerError(wrappedErr), nil
}
fileContent, contentType, apiErr := ctrl.getMultipartFile(file)
if apiErr != nil {
logger.WithError(apiErr).Errorf("problem getting multipart file %s", file.Name)
logger.ErrorContext(
ctx, "problem getting multipart file",
slog.String("error", apiErr.Error()),
slog.String("fileName", file.Name),
)
return apiErr, nil
}
defer fileContent.Close()
@@ -102,14 +115,24 @@ func (ctrl *Controller) ReplaceFile( //nolint:funlen,ireturn
if apiErr := ctrl.scanAndReportVirus(
ctx, fileContent, file.ID, file.Name, sessionHeaders,
); apiErr != nil {
logger.WithError(apiErr).Errorf("problem scanning file %s for viruses", file.Name)
logger.ErrorContext(
ctx, "problem scanning file for viruses",
slog.String("error", apiErr.Error()),
slog.String("fileName", file.Name),
)
return apiErr, nil
}
if apiErr := ctrl.metadataStorage.SetIsUploaded(
ctx, file.ID, false, sessionHeaders,
); apiErr != nil {
logger.WithError(apiErr).Errorf("problem flagging file as pending upload %s", file.Name)
logger.ErrorContext(
ctx, "problem flagging file as pending upload",
slog.String("error", apiErr.Error()),
slog.String("fileName", file.Name),
)
return apiErr, nil
}
@@ -117,7 +140,11 @@ func (ctrl *Controller) ReplaceFile( //nolint:funlen,ireturn
if apiErr != nil {
// let's revert the change to isUploaded
_ = ctrl.metadataStorage.SetIsUploaded(ctx, file.ID, true, sessionHeaders)
logger.WithError(apiErr).Errorf("problem uploading file %s to storage", file.Name)
logger.ErrorContext(
ctx, "problem uploading file to storage",
slog.String("error", apiErr.Error()),
slog.String("fileName", file.Name),
)
return apiErr, nil
}
@@ -129,7 +156,12 @@ func (ctrl *Controller) ReplaceFile( //nolint:funlen,ireturn
sessionHeaders,
)
if apiErr != nil {
logger.WithError(apiErr).Errorf("problem populating file metadata for file %s", file.Name)
logger.ErrorContext(
ctx, "problem populating file metadata for file",
slog.String("error", apiErr.Error()),
slog.String("fileName", file.Name),
)
return apiErr, nil
}

View File

@@ -3,7 +3,9 @@ package controller_test
import (
"bytes"
"io"
"log/slog"
"mime/multipart"
"os"
"strings"
"testing"
"time"
@@ -13,7 +15,6 @@ import (
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/controller"
"github.com/nhost/nhost/services/storage/controller/mock"
"github.com/sirupsen/logrus"
gomock "go.uber.org/mock/gomock"
)
@@ -67,8 +68,9 @@ func TestReplaceFile(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
logger := logrus.New()
logger.SetLevel(logrus.ErrorLevel)
logger := slog.New(
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}),
)
file := fakeFile{
contents: "some content",

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"log/slog"
"mime/multipart"
"net/http"
@@ -253,19 +254,25 @@ func (ctrl *Controller) UploadFiles( //nolint:ireturn
form, err := request.Body.ReadForm(maxFormMemory)
if err != nil {
logger.WithError(err).Error("problem reading multipart form")
logger.ErrorContext(
ctx, "problem reading multipart form", slog.String("error", err.Error()),
)
return InternalServerError(err), nil
}
uploadFilesRequest, apiErr := parseUploadRequest(form)
if apiErr != nil {
logger.WithError(apiErr).Error("problem parsing upload request")
logger.ErrorContext(
ctx, "problem parsing upload request", slog.String("error", apiErr.Error()),
)
return apiErr, nil
}
fm, apiErr := ctrl.upload(ctx, uploadFilesRequest, sessionHeaders)
if apiErr != nil {
logger.WithError(apiErr).Error("problem uploading files")
logger.ErrorContext(ctx, "problem uploading files", slog.String("error", apiErr.Error()))
return api.UploadFilesdefaultJSONResponse{
Body: api.ErrorResponseWithProcessedFiles{

View File

@@ -5,8 +5,10 @@ import (
"encoding/json"
"fmt"
"io"
"log/slog"
"mime/multipart"
"net/textproto"
"os"
"strings"
"testing"
"time"
@@ -16,7 +18,6 @@ import (
"github.com/nhost/nhost/services/storage/api"
"github.com/nhost/nhost/services/storage/controller"
"github.com/nhost/nhost/services/storage/controller/mock"
"github.com/sirupsen/logrus"
gomock "go.uber.org/mock/gomock"
)
@@ -115,8 +116,9 @@ func TestUploadFile(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
logger := logrus.New()
logger.SetLevel(logrus.ErrorLevel)
logger := slog.New(
slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}),
)
files := []fakeFile{
{

View File

@@ -1,16 +1,77 @@
package main
import (
"context"
"log"
"os"
"github.com/nhost/nhost/services/storage/cmd"
docs "github.com/urfave/cli-docs/v3"
"github.com/urfave/cli/v3"
)
const (
flagOutput = "output"
)
var Version string
func markdownDocs() *cli.Command {
return &cli.Command{ //nolint:exhaustruct
Name: "docs",
Usage: "Generate markdown documentation for the CLI",
Flags: []cli.Flag{
&cli.StringFlag{ //nolint:exhaustruct
Name: flagOutput,
Usage: "Output file (default: stdout)",
Value: "",
},
},
Action: func(_ context.Context, cmd *cli.Command) error {
md, err := docs.ToMarkdown(cmd.Root())
if err != nil {
return cli.Exit("failed to generate markdown documentation: "+err.Error(), 1)
}
out := os.Stdout
if output := cmd.String(flagOutput); output != "" {
out, err = os.OpenFile(
output,
os.O_CREATE|os.O_WRONLY|os.O_TRUNC,
0o644, //nolint:mnd
)
if err != nil {
return cli.Exit("failed to open output file: "+err.Error(), 1)
}
}
defer out.Close()
if _, err := out.WriteString(md); err != nil {
return cli.Exit("failed to write markdown documentation: "+err.Error(), 1)
}
return nil
},
}
}
//go:generate oapi-codegen -config api/server.cfg.yaml controller/openapi.yaml
//go:generate oapi-codegen -config api/types.cfg.yaml controller/openapi.yaml
//go:generate gqlgenc
func main() {
if err := cmd.Execute(); err != nil {
os.Exit(1)
serveCmd := cmd.CommandServe()
app := &cli.Command{ //nolint:exhaustruct
Name: "storage",
Version: Version,
Usage: "Nhost Storage API server",
Flags: serveCmd.Flags,
Commands: []*cli.Command{
markdownDocs(),
},
Action: serveCmd.Action,
}
if err := app.Run(context.Background(), os.Args); err != nil {
log.Fatal(err)
}
}

View File

@@ -13,10 +13,10 @@ curl -D - -X POST --location "https://api.fastly.com/tokens" \
import (
"context"
"fmt"
"log/slog"
"net/http"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
const (
@@ -66,7 +66,7 @@ func (fst *fastly) purge(ctx context.Context, key string) error {
return nil
}
func New(serviceID string, apiKey string, logger *logrus.Logger) gin.HandlerFunc {
func New(serviceID string, apiKey string, logger *slog.Logger) gin.HandlerFunc {
fst := &fastly{serviceID, apiKey}
return func(ctx *gin.Context) {
@@ -87,10 +87,16 @@ func New(serviceID string, apiKey string, logger *logrus.Logger) gin.HandlerFunc
}
if id := ctx.GetString(fileChangedContextKey); id != "" {
logger.WithField("key", id).Debug("purging file from cdn")
logger.InfoContext(
ctx.Request.Context(), "purging file from cdn", slog.String("key", id),
)
if err := fst.purge(ctx, id); err != nil {
logger.WithField("key", id).WithError(err).Error("failed to purge file from cdn")
logger.ErrorContext(
ctx.Request.Context(),
"failed to purge file from cdn",
slog.String("key", id), slog.String("error", err.Error()),
)
}
}
}

View File

@@ -2,35 +2,35 @@ package middleware
import (
"context"
"log/slog"
"time"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
type loggerCtxKey struct{}
// Stores the logger in the context.
func LoggerToContext(ctx context.Context, logger logrus.FieldLogger) context.Context {
func LoggerToContext(ctx context.Context, logger *slog.Logger) context.Context {
return context.WithValue(ctx, loggerCtxKey{}, logger)
}
// Retrieves the logger from the context. It creates a new one if it can't be found.
func LoggerFromContext(ctx context.Context) logrus.FieldLogger { //nolint:contextcheck,ireturn
func LoggerFromContext(ctx context.Context) *slog.Logger { //nolint:contextcheck
ginCtx, ok := ctx.(*gin.Context)
if ok {
ctx = ginCtx.Request.Context()
}
logger, ok := ctx.Value(loggerCtxKey{}).(logrus.FieldLogger)
logger, ok := ctx.Value(loggerCtxKey{}).(*slog.Logger)
if !ok {
return logrus.New()
return slog.Default()
}
return logger
}
func Logger(logger logrus.FieldLogger) gin.HandlerFunc {
func Logger(logger *slog.Logger) gin.HandlerFunc {
return func(ctx *gin.Context) {
startTime := time.Now()
@@ -39,14 +39,20 @@ func Logger(logger logrus.FieldLogger) gin.HandlerFunc {
clientIP := ctx.ClientIP()
reqMethod := ctx.Request.Method
reqURL := ctx.Request.RequestURI
logger := logger.WithFields(logrus.Fields{
"trace_id": trace.TraceID,
"span_id": trace.SpanID,
"parent_span_id": trace.ParentSpanID,
"client_ip": clientIP,
"request_method": reqMethod,
"request_url": reqURL,
})
logger := logger.With(
slog.Group(
"trace",
slog.String("trace_id", trace.TraceID),
slog.String("span_id", trace.SpanID),
slog.String("parent_span_id", trace.ParentSpanID),
),
slog.Group(
"request",
slog.String("client_ip", clientIP),
slog.String("method", reqMethod),
slog.String("url", reqURL),
),
)
ctx.Request = ctx.Request.WithContext(
LoggerToContext(ctx.Request.Context(), logger),
)
@@ -55,18 +61,19 @@ func Logger(logger logrus.FieldLogger) gin.HandlerFunc {
latencyTime := time.Since(startTime)
statusCode := ctx.Writer.Status()
logger = logger.WithFields(logrus.Fields{
"status_code": statusCode,
"latency_time": latencyTime,
"errors": ctx.Errors.Errors(),
})
logger = logger.With(slog.Group(
"response",
slog.Int("status_code", statusCode),
slog.Duration("latency_time", latencyTime),
slog.Any("errors", ctx.Errors.Errors()),
))
TraceToHTTPHeaders(trace, ctx.Writer.Header())
if len(ctx.Errors.Errors()) > 0 {
logger.Error("call completed with errors")
logger.ErrorContext(ctx, "call completed with errors")
} else {
logger.Info("call completed")
logger.InfoContext(ctx, "call completed")
}
}
}

View File

@@ -20,7 +20,7 @@ type hasuraErrResponse struct {
Code string `json:"code"`
}
func postMetadata(baseURL, hasuraSecret string, data interface{}) error {
func postMetadata(ctx context.Context, baseURL, hasuraSecret string, data any) error {
client := &http.Client{ //nolint: exhaustruct
Timeout: time.Second * timeout,
}
@@ -31,7 +31,7 @@ func postMetadata(baseURL, hasuraSecret string, data interface{}) error {
}
req, err := http.NewRequestWithContext(
context.Background(),
ctx,
http.MethodPost,
baseURL+"/metadata",
bytes.NewBuffer(b),
@@ -154,7 +154,9 @@ type DropRelationshipArgs struct {
Relationship string `json:"relationship"`
}
func ApplyHasuraMetadata(url, hasuraSecret, hasuraDBName string) error { //nolint: funlen
func ApplyHasuraMetadata( //nolint: funlen
ctx context.Context, url, hasuraSecret, hasuraDBName string,
) error {
bucketsTable := TrackTable{
Type: "pg_track_table",
Args: PgTrackTableArgs{
@@ -190,7 +192,7 @@ func ApplyHasuraMetadata(url, hasuraSecret, hasuraDBName string) error { //nolin
},
}
if err := postMetadata(url, hasuraSecret, bucketsTable); err != nil {
if err := postMetadata(ctx, url, hasuraSecret, bucketsTable); err != nil {
return fmt.Errorf("problem adding metadata for the buckets table: %w", err)
}
@@ -232,7 +234,7 @@ func ApplyHasuraMetadata(url, hasuraSecret, hasuraDBName string) error { //nolin
},
}
if err := postMetadata(url, hasuraSecret, filesTable); err != nil {
if err := postMetadata(ctx, url, hasuraSecret, filesTable); err != nil {
return fmt.Errorf("problem adding metadata for the files table: %w", err)
}
@@ -270,7 +272,7 @@ func ApplyHasuraMetadata(url, hasuraSecret, hasuraDBName string) error { //nolin
},
}
if err := postMetadata(url, hasuraSecret, virusTable); err != nil {
if err := postMetadata(ctx, url, hasuraSecret, virusTable); err != nil {
return fmt.Errorf("problem adding metadata for the virus table: %w", err)
}
@@ -289,7 +291,7 @@ func ApplyHasuraMetadata(url, hasuraSecret, hasuraDBName string) error { //nolin
},
}
if err := postMetadata(url, hasuraSecret, objRelationshipBuckets); err != nil {
if err := postMetadata(ctx, url, hasuraSecret, objRelationshipBuckets); err != nil {
return fmt.Errorf("problem creaiing object relationship for buckets: %w", err)
}
@@ -314,7 +316,7 @@ func ApplyHasuraMetadata(url, hasuraSecret, hasuraDBName string) error { //nolin
},
}
if err := postMetadata(url, hasuraSecret, arrRelationship); err != nil {
if err := postMetadata(ctx, url, hasuraSecret, arrRelationship); err != nil {
return fmt.Errorf("problem creating array relationships: %w", err)
}
@@ -333,7 +335,7 @@ func ApplyHasuraMetadata(url, hasuraSecret, hasuraDBName string) error { //nolin
},
}
if err := postMetadata(url, hasuraSecret, objRelationshipVirusFile); err != nil {
if err := postMetadata(ctx, url, hasuraSecret, objRelationshipVirusFile); err != nil {
return fmt.Errorf("problem creaiing object relationship for buckets: %w", err)
}

View File

@@ -1,14 +0,0 @@
---
debug: true
hasura-endpoint: http://localhost:8080/v1
hasura-metadata: true
hasura-graphql-admin-secret: nhost-admin-secret
s3-endpoint: "http://localhost:9000"
s3-access-key: "5a7bdb5f42c41e0622bf61d6e08d5537"
s3-secret-key: "9e1c40c65a615a5b52f52aeeaf549944ec53acb1dff4a0bf01fb58e969f915c8"
s3-disable-http: true
s3-region: "us-east-1"
s3-bucket: "default"
s3-root-folder: "f215cf48-7458-4596-9aa5-2159fc6a3caf"
postgres-migrations: true
postgres-migrations-source: postgres://postgres:hejsan@localhost:5432/postgres?sslmode=disable

View File

@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"net/url"
"strconv"
@@ -15,7 +16,6 @@ import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/nhost/nhost/services/storage/controller"
"github.com/sirupsen/logrus"
)
func deptr[T any](p *T) T { //nolint:ireturn
@@ -63,7 +63,7 @@ type S3 struct {
bucket *string
rootFolder string
url string
logger *logrus.Logger
logger *slog.Logger
}
func NewS3(
@@ -71,7 +71,7 @@ func NewS3(
bucket string,
rootFolder string,
url string,
logger *logrus.Logger,
logger *slog.Logger,
) *S3 {
return &S3{
client: client,

View File

@@ -6,6 +6,7 @@ package storage_test
import (
"context"
"io"
"log/slog"
"net/http"
"os"
"slices"
@@ -21,11 +22,10 @@ import (
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/nhost/nhost/services/storage/controller"
"github.com/nhost/nhost/services/storage/storage"
"github.com/sirupsen/logrus"
)
func getS3() *storage.S3 {
logger := logrus.New()
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
ctx := context.Background()
config, err := config.LoadDefaultConfig(ctx,
config.WithRegion("eu-central-1"),
@@ -38,7 +38,7 @@ func getS3() *storage.S3 {
),
)
if err != nil {
logger.Error(err)
logger.ErrorContext(ctx, "config load failed", slog.String("error", err.Error()))
}
url := "http://localhost:9000"
client := s3.NewFromConfig(config,

View File

@@ -91,6 +91,10 @@ export interface SignInProviderParams {
export interface Client {
baseURL: string;
/** Add a middleware function to the fetch chain
* @param chainFunction - The middleware function to add
*/
pushChainFunction(chainFunction: ChainFunction): void;
/**
Summary: Sign in with an OAuth2 provider

View File

@@ -610,6 +610,10 @@ export interface VerifyTicketParams {
export interface Client {
baseURL: string;
/** Add a middleware function to the fetch chain
* @param chainFunction - The middleware function to add
*/
pushChainFunction(chainFunction: ChainFunction): void;
/**
Summary: Refresh access token

View File

@@ -138,6 +138,10 @@ export interface SimpleObject {
export interface Client {
baseURL: string;
/** Add a middleware function to the fetch chain
* @param chainFunction - The middleware function to add
*/
pushChainFunction(chainFunction: ChainFunction): void;};

View File

@@ -1,6 +1,10 @@
{{- define "client_interface" }}
export interface Client {
baseURL: string;
/** Add a middleware function to the fetch chain
* @param chainFunction - The middleware function to add
*/
pushChainFunction(chainFunction: ChainFunction): void;
{{- range .Methods }}
{{- $method := . }}

View File

@@ -1,14 +0,0 @@
freebsd_task:
name: 'FreeBSD'
freebsd_instance:
image_family: freebsd-14-2
install_script:
- pkg update -f
- pkg install -y go
test_script:
# run tests as user "cirrus" instead of root
- pw useradd cirrus -m
- chown -R cirrus:cirrus .
- FSNOTIFY_BUFFER=4096 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
- sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
- FSNOTIFY_DEBUG=1 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race -v ./...

View File

@@ -1,10 +0,0 @@
# go test -c output
*.test
*.test.exe
# Output of go build ./cmd/fsnotify
/fsnotify
/fsnotify.exe
/test/kqueue
/test/a.out

View File

@@ -1,2 +0,0 @@
Chris Howey <howeyc@gmail.com> <chris@howey.me>
Nathan Youngman <git@nathany.com> <4566+nathany@users.noreply.github.com>

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