Compare commits
26 Commits
@nhost/das
...
@nhost/str
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c1996b13b | ||
|
|
e3d90fd5d2 | ||
|
|
af016e1caa | ||
|
|
ed4c780115 | ||
|
|
941f0f5755 | ||
|
|
75344b2bc0 | ||
|
|
65da426e8b | ||
|
|
f34702f3c5 | ||
|
|
6cb70eee01 | ||
|
|
9395c9687f | ||
|
|
eb1eb934a4 | ||
|
|
c62fed2c9a | ||
|
|
16fe1a47da | ||
|
|
0f04e8b8b8 | ||
|
|
e6dad4d696 | ||
|
|
bcb3b79add | ||
|
|
fe658231b4 | ||
|
|
a1188b7d98 | ||
|
|
cd4bdc581d | ||
|
|
4e2f8ccd52 | ||
|
|
8a6d8c7534 | ||
|
|
fa75409f09 | ||
|
|
74662052ae | ||
|
|
37ab5fe878 | ||
|
|
be9af96fa7 | ||
|
|
31abbe5f30 |
@@ -1,5 +1,23 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 0.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- ed4c7801: chore(dashboard): remove Functions section
|
||||
|
||||
## 0.9.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4e2f8ccd: fix(dashboard): don't break Auth page in local mode
|
||||
|
||||
## 0.9.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 31abbe5f: fix(dashboard): enable toggle when settings are filled in
|
||||
|
||||
## 0.9.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Nhost Dashboard
|
||||
|
||||
This is the Nhost Dashboard, a web application that allows you to manage your Nhost project.
|
||||
This is the Nhost Dashboard, a web application that allows you to manage your Nhost projects.
|
||||
To get started, you need to have an Nhost project. If you don't have one, you can [create a project here](https://app.nhost.io).
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "0.9.8",
|
||||
"version": "0.10.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
@@ -8,7 +8,7 @@
|
||||
"build": "next build --no-lint",
|
||||
"analyze": "ANALYZE=true pnpm build --no-lint",
|
||||
"start": "next start",
|
||||
"lint": "next lint --max-warnings 3",
|
||||
"lint": "next lint --max-warnings 2",
|
||||
"test": "vitest",
|
||||
"codegen": "graphql-codegen --config graphql.config.yaml --errors-only",
|
||||
"nhost:dev": "nhost dev -d",
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export type FunctionLog = {
|
||||
name: string;
|
||||
language: string;
|
||||
logs: { date: string; message: string; createdAt: string }[];
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
import { Text } from '@/ui/Text';
|
||||
import { ChevronRightIcon } from '@heroicons/react/solid';
|
||||
import { formatDistance } from 'date-fns';
|
||||
|
||||
export interface FunctionLogDataEntryProps {
|
||||
time: string;
|
||||
nav: string;
|
||||
}
|
||||
|
||||
export function FunctionLogDataEntry({ time, nav }: FunctionLogDataEntryProps) {
|
||||
return (
|
||||
<a href={`#${nav}`}>
|
||||
<div className="flex cursor-pointer flex-row place-content-between border-t py-3">
|
||||
<Text
|
||||
color="greyscaleDark"
|
||||
variant="body"
|
||||
className="flex font-medium"
|
||||
size="tiny"
|
||||
>
|
||||
{formatDistance(new Date(time), new Date(), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</Text>
|
||||
<ChevronRightIcon className="ml-2 h-4 w-4 cursor-pointer self-center text-greyscaleDark" />
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export default FunctionLogDataEntry;
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Text } from '@/ui/Text';
|
||||
import { FunctionLogDataEntry } from './FunctionLogDataEntry';
|
||||
|
||||
export interface FunctionLogHistoryProps {
|
||||
logs?: Log[];
|
||||
}
|
||||
|
||||
type Log = {
|
||||
createdAt: string;
|
||||
date: any;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export function FunctionLogHistory({ logs }: FunctionLogHistoryProps) {
|
||||
return (
|
||||
<div className=" mx-auto max-w-6xl pt-10">
|
||||
<div className="flex flex-row place-content-between">
|
||||
<div className="flex">
|
||||
<Text size="large" className="font-medium" color="greyscaleDark">
|
||||
Log History
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 flex flex-col">
|
||||
<div className="flex flex-row">
|
||||
<Text className="font-semibold" size="normal" color="greyscaleDark">
|
||||
Time
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
{logs ? (
|
||||
<div>
|
||||
{logs.slice(0, 4).map((log: Log) => (
|
||||
<FunctionLogDataEntry
|
||||
time={log.createdAt}
|
||||
nav={`#-${log.date}`}
|
||||
key={`${log.date}-${log.message.slice(66)}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="pt-1 pl-0.5 font-mono text-xs text-greyscaleDark">
|
||||
No log history.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FunctionLogHistory;
|
||||
@@ -1,89 +0,0 @@
|
||||
import { normalizeToIndividualFunctionsWithLogs } from '@/components/applications/functions/normalizeToIndividualFunctionsWithLogs';
|
||||
import terminalTheme from '@/data/terminalTheme';
|
||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||
import { useGetFunctionLogQuery } from '@/utils/__generated__/graphql';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import json from 'react-syntax-highlighter/dist/cjs/languages/hljs/json';
|
||||
import { FunctionLogHistory } from './FunctionLogHistory';
|
||||
|
||||
SyntaxHighlighter.registerLanguage('json', json);
|
||||
|
||||
export function FunctionsLogsTerminalPage({ functionName }: any) {
|
||||
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
||||
const [normalizedFunctionData, setNormalizedFunctionData] = useState(null);
|
||||
|
||||
const { data, startPolling } = useGetFunctionLogQuery({
|
||||
variables: {
|
||||
subdomain: currentApplication.subdomain,
|
||||
functionPaths: [functionName?.split('/').slice(1, 3).join('/')],
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
startPolling(3000);
|
||||
}, [startPolling]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data || data.getFunctionLogs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setNormalizedFunctionData(
|
||||
normalizeToIndividualFunctionsWithLogs(data.getFunctionLogs)[0],
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
if (
|
||||
!data ||
|
||||
data.getFunctionLogs.length === 0 ||
|
||||
!normalizedFunctionData ||
|
||||
normalizedFunctionData.logs.length === 0
|
||||
) {
|
||||
return (
|
||||
<div className="w-full rounded-lg text-white">
|
||||
<div className="h-terminal overflow-auto rounded-lg bg-log px-4 py-4 font-mono shadow-sm">
|
||||
<div className="font-mono text-xs text-grey">
|
||||
There are no stored logs yet. Try calling your function for logs to
|
||||
appear.
|
||||
</div>
|
||||
</div>
|
||||
<FunctionLogHistory />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="w-full rounded-lg text-white">
|
||||
<div className="h-terminal overflow-auto rounded-lg bg-log px-4 py-4 font-mono shadow-sm">
|
||||
{normalizedFunctionData.logs.map((log) => (
|
||||
<div
|
||||
key={`${log.date}-${log.message.slice(66)}`}
|
||||
className=" flex text-sm"
|
||||
>
|
||||
<div id={`#-${log.date}`}>
|
||||
<pre className="inline">
|
||||
<span className="mr-4 text-greyscaleGrey">{log.date}</span>{' '}
|
||||
<span className="">
|
||||
{' '}
|
||||
<SyntaxHighlighter
|
||||
style={terminalTheme}
|
||||
customStyle={{
|
||||
display: 'inline',
|
||||
}}
|
||||
className="inline-flex"
|
||||
language="json"
|
||||
>
|
||||
{log.message}
|
||||
</SyntaxHighlighter>
|
||||
</span>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<FunctionLogHistory logs={normalizedFunctionData.logs} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FunctionsLogsTerminalPage;
|
||||
@@ -1,5 +0,0 @@
|
||||
export type FunctionResponseLog = {
|
||||
functionPath: string;
|
||||
createdAt: string;
|
||||
message: string;
|
||||
};
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Button } from '@/ui/Button';
|
||||
import Loading from '@/ui/Loading';
|
||||
import { Text } from '@/ui/Text';
|
||||
import Image from 'next/image';
|
||||
|
||||
export function FunctionsNotDeployed() {
|
||||
return (
|
||||
<div className="mx-auto mt-12 max-w-2xl text-center">
|
||||
<div className="mx-auto flex w-centImage flex-col text-center">
|
||||
<Image
|
||||
src="/terminal-text.svg"
|
||||
alt="Terminal with a green dot"
|
||||
width={72}
|
||||
height={72}
|
||||
/>
|
||||
</div>
|
||||
<Text className="mt-4 font-medium" size="large" color="dark">
|
||||
Functions Logs
|
||||
</Text>
|
||||
<Text size="normal" color="greyscaleDark" className="mt-1 transform">
|
||||
Once you deploy a function, you can view the logs here.
|
||||
</Text>
|
||||
<div className="mt-1.5 flex text-center">
|
||||
<Button
|
||||
Component="a"
|
||||
transparent
|
||||
color="blue"
|
||||
className="mx-auto cursor-pointer font-medium"
|
||||
href="https://docs.nhost.io/platform/serverless-functions"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Read more
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-24 flex flex-col text-center">
|
||||
<Loading />
|
||||
<Text size="normal" color="greyscaleDark" className="mt-1 transform">
|
||||
Awaiting new requests…
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FunctionsNotDeployed;
|
||||
@@ -1,79 +0,0 @@
|
||||
export type FinalFunction = {
|
||||
folder: string;
|
||||
funcs: Func[];
|
||||
nestedLevel: number;
|
||||
parentFolder?: string;
|
||||
};
|
||||
|
||||
export type Func = {
|
||||
name: string;
|
||||
id: string;
|
||||
lang: string;
|
||||
functionName: string;
|
||||
route?: string;
|
||||
path?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
createdWithCommitSha?: string;
|
||||
formattedCreatedAt?: string;
|
||||
formattedUpdatedAt?: string;
|
||||
};
|
||||
|
||||
export const normalizeFunctionMetadata = (functions): FinalFunction[] => {
|
||||
const finalFunctions: FinalFunction[] = [
|
||||
{ folder: 'functions', funcs: [], nestedLevel: 0 },
|
||||
];
|
||||
const topLevelFunctionsFolder = finalFunctions[0].funcs;
|
||||
functions.forEach((func) => {
|
||||
const nestedLevel = func.path?.split('/').length;
|
||||
const newFuncToAdd = {
|
||||
...func,
|
||||
name: func.path?.split('/')[nestedLevel - 1],
|
||||
lang: func.path?.split('.')[1],
|
||||
// formattedCreatedAt: `${format(
|
||||
// parseISO(func.createdAt),
|
||||
// 'yyyy-MM-dd HH:mm:ss',
|
||||
// )}`,
|
||||
// formattedUpdatedAt: `${formatDistanceToNowStrict(
|
||||
// parseISO(func.updatedAt),
|
||||
// {
|
||||
// addSuffix: true,
|
||||
// },
|
||||
// )}`,
|
||||
};
|
||||
|
||||
if (nestedLevel === 2) {
|
||||
topLevelFunctionsFolder.push(newFuncToAdd);
|
||||
} else if (nestedLevel > 2) {
|
||||
const nameOfTheFolder = func.path?.split('/')[nestedLevel - 2];
|
||||
const nameOfParentFolder = func.path?.split('/')[nestedLevel - 3];
|
||||
const checkForFolderExistence = finalFunctions.find(
|
||||
(functionFolder) => functionFolder.folder === nameOfTheFolder,
|
||||
);
|
||||
|
||||
if (!checkForFolderExistence) {
|
||||
finalFunctions.push({
|
||||
folder: nameOfTheFolder,
|
||||
funcs: [newFuncToAdd],
|
||||
nestedLevel: nestedLevel - 2,
|
||||
parentFolder: nameOfParentFolder,
|
||||
});
|
||||
} else {
|
||||
checkForFolderExistence.funcs.push(newFuncToAdd);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Sort folders by putting the subfolder next to their parent folder, even though they share the same place in the array
|
||||
// except for the nestedLevel prop. A future change to this would be to make folders have subfolders, which is easier
|
||||
// understand, but would require a change in the UI.
|
||||
// @TODO: Change to have elements have subfolders inside the object?
|
||||
finalFunctions.sort((a, b) => {
|
||||
if (a.folder === b.parentFolder) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
|
||||
return finalFunctions;
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
import { format, parseISO } from 'date-fns';
|
||||
import type { FunctionLog } from './FunctionLog';
|
||||
import type { FunctionResponseLog } from './FunctionResponseLog';
|
||||
|
||||
export const normalizeToIndividualFunctionsWithLogs = (
|
||||
functionLogs: FunctionResponseLog[],
|
||||
) => {
|
||||
const arrayOfFunctions: FunctionLog[] = [];
|
||||
const sortedFunctions = [...functionLogs].sort(
|
||||
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
|
||||
);
|
||||
|
||||
sortedFunctions.forEach((functionLog) => {
|
||||
const funcName = functionLog.functionPath;
|
||||
const logMessage = {
|
||||
createdAt: functionLog.createdAt,
|
||||
date: `${format(parseISO(functionLog.createdAt), 'yyyy-MM-dd HH:mm:ss')}`,
|
||||
message: functionLog.message,
|
||||
};
|
||||
const newFunc = {
|
||||
name: funcName,
|
||||
language: functionLog.functionPath.split('.')[1],
|
||||
logs: [logMessage],
|
||||
};
|
||||
// If the function is already in the array of functions to log, just add the new log message to the existing object...
|
||||
if (arrayOfFunctions.some((obj) => obj.name === funcName)) {
|
||||
const index = arrayOfFunctions.findIndex((obj) => obj.name === funcName);
|
||||
const currentFunction = arrayOfFunctions[index];
|
||||
currentFunction.logs.push(logMessage);
|
||||
} else {
|
||||
// If the function is not in the array of functions, add it with the log message to it.
|
||||
arrayOfFunctions.push(newFunc);
|
||||
}
|
||||
});
|
||||
|
||||
return arrayOfFunctions;
|
||||
};
|
||||
|
||||
export default normalizeToIndividualFunctionsWithLogs;
|
||||
@@ -5,12 +5,16 @@ import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAn
|
||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||
import Input from '@/ui/v2/Input';
|
||||
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
||||
import { useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export interface AllowedEmailSettingsFormValues {
|
||||
/**
|
||||
* Determines whether or not the allowed email settings are enabled.
|
||||
*/
|
||||
enabled: boolean;
|
||||
/**
|
||||
* Set of email that are allowed to be used for project's users authentication.
|
||||
*/
|
||||
@@ -25,7 +29,6 @@ export interface AllowedEmailSettingsFormValues {
|
||||
export default function AllowedEmailDomainsSettings() {
|
||||
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
||||
const [updateApp] = useUpdateAppMutation();
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
|
||||
const { data, loading, error } = useGetAppQuery({
|
||||
variables: {
|
||||
@@ -36,12 +39,30 @@ export default function AllowedEmailDomainsSettings() {
|
||||
const form = useForm<AllowedEmailSettingsFormValues>({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: {
|
||||
enabled:
|
||||
Boolean(data?.app?.authAccessControlAllowedEmails) ||
|
||||
Boolean(data?.app?.authAccessControlAllowedEmailDomains),
|
||||
authAccessControlAllowedEmails: data?.app?.authAccessControlAllowedEmails,
|
||||
authAccessControlAllowedEmailDomains:
|
||||
data?.app?.authAccessControlAllowedEmailDomains,
|
||||
},
|
||||
});
|
||||
|
||||
const { register, formState, setValue, watch } = form;
|
||||
const enabled = watch('enabled');
|
||||
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!data.app?.authAccessControlAllowedEmails &&
|
||||
!data.app?.authAccessControlAllowedEmailDomains
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setValue('enabled', true, { shouldDirty: false });
|
||||
}, [data.app, setValue]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -56,8 +77,6 @@ export default function AllowedEmailDomainsSettings() {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const { register, formState } = form;
|
||||
|
||||
const handleAllowedEmailDomainsChange = async (
|
||||
values: AllowedEmailSettingsFormValues,
|
||||
) => {
|
||||
@@ -65,7 +84,12 @@ export default function AllowedEmailDomainsSettings() {
|
||||
variables: {
|
||||
id: currentApplication.id,
|
||||
app: {
|
||||
...values,
|
||||
authAccessControlAllowedEmails: values.enabled
|
||||
? values.authAccessControlAllowedEmails
|
||||
: '',
|
||||
authAccessControlAllowedEmailDomains: values.enabled
|
||||
? values.authAccessControlAllowedEmailDomains
|
||||
: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -89,13 +113,17 @@ export default function AllowedEmailDomainsSettings() {
|
||||
<SettingsContainer
|
||||
title="Allowed Emails and Domains"
|
||||
description="Allow specific email addresses and domains to sign up."
|
||||
primaryActionButtonProps={{
|
||||
disabled: !formState.isValid || !formState.isDirty,
|
||||
loading: formState.isSubmitting,
|
||||
slotProps={{
|
||||
submitButton: {
|
||||
disabled: !formState.isValid || !isDirty,
|
||||
loading: formState.isSubmitting,
|
||||
},
|
||||
}}
|
||||
docsLink="https://docs.nhost.io/platform/authentication"
|
||||
enabled={enabled}
|
||||
onEnabledChange={setEnabled}
|
||||
onEnabledChange={(switchEnabled) =>
|
||||
setValue('enabled', switchEnabled, { shouldDirty: true })
|
||||
}
|
||||
showSwitch
|
||||
className={twMerge(
|
||||
'row-span-2 grid grid-flow-row gap-4 px-4 lg:grid-cols-3',
|
||||
|
||||
@@ -5,12 +5,16 @@ import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAn
|
||||
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
|
||||
import Input from '@/ui/v2/Input';
|
||||
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
||||
import { useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export interface BlockedEmailFormValues {
|
||||
/**
|
||||
* Determines whether or not the blocked email settings are enabled.
|
||||
*/
|
||||
enabled: boolean;
|
||||
/**
|
||||
* Set of emails that are blocked from registering to the user's project.
|
||||
*/
|
||||
@@ -24,7 +28,6 @@ export interface BlockedEmailFormValues {
|
||||
export default function BlockedEmailSettings() {
|
||||
const { currentApplication } = useCurrentWorkspaceAndApplication();
|
||||
const [updateApp] = useUpdateAppMutation();
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
|
||||
const { data, loading, error } = useGetAppQuery({
|
||||
variables: {
|
||||
@@ -35,12 +38,30 @@ export default function BlockedEmailSettings() {
|
||||
const form = useForm<BlockedEmailFormValues>({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: {
|
||||
enabled:
|
||||
Boolean(data?.app?.authAccessControlBlockedEmails) ||
|
||||
Boolean(data?.app?.authAccessControlBlockedEmailDomains),
|
||||
authAccessControlBlockedEmails: data?.app?.authAccessControlBlockedEmails,
|
||||
authAccessControlBlockedEmailDomains:
|
||||
data?.app?.authAccessControlBlockedEmailDomains,
|
||||
},
|
||||
});
|
||||
|
||||
const { register, formState, setValue, watch } = form;
|
||||
const enabled = watch('enabled');
|
||||
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!data.app?.authAccessControlBlockedEmails &&
|
||||
!data.app?.authAccessControlBlockedEmailDomains
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setValue('enabled', true, { shouldDirty: false });
|
||||
}, [data.app, setValue]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -55,8 +76,6 @@ export default function BlockedEmailSettings() {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const { register, formState } = form;
|
||||
|
||||
const handleAllowedEmailDomainsChange = async (
|
||||
values: BlockedEmailFormValues,
|
||||
) => {
|
||||
@@ -64,7 +83,12 @@ export default function BlockedEmailSettings() {
|
||||
variables: {
|
||||
id: currentApplication.id,
|
||||
app: {
|
||||
...values,
|
||||
authAccessControlBlockedEmails: values.enabled
|
||||
? values.authAccessControlBlockedEmails
|
||||
: '',
|
||||
authAccessControlBlockedEmailDomains: values.enabled
|
||||
? values.authAccessControlBlockedEmailDomains
|
||||
: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -88,13 +112,17 @@ export default function BlockedEmailSettings() {
|
||||
<SettingsContainer
|
||||
title="Blocked Emails and Domains"
|
||||
description="Block specific email addresses and domains to sign up."
|
||||
primaryActionButtonProps={{
|
||||
disabled: !formState.isValid || !formState.isDirty,
|
||||
loading: formState.isSubmitting,
|
||||
slotProps={{
|
||||
submitButton: {
|
||||
disabled: !formState.isValid || !isDirty,
|
||||
loading: formState.isSubmitting,
|
||||
},
|
||||
}}
|
||||
docsLink="https://docs.nhost.io/platform/authentication"
|
||||
enabled={enabled}
|
||||
onEnabledChange={setEnabled}
|
||||
onEnabledChange={(switchEnabled) =>
|
||||
setValue('enabled', switchEnabled, { shouldDirty: true })
|
||||
}
|
||||
showSwitch
|
||||
className={twMerge(
|
||||
'row-span-2 grid grid-flow-row gap-4 px-4 lg:grid-cols-3',
|
||||
|
||||
@@ -67,8 +67,8 @@ export default function CreateUserForm({
|
||||
} = form;
|
||||
|
||||
const baseAuthUrl = generateAppServiceUrl(
|
||||
currentApplication.subdomain,
|
||||
currentApplication.region.awsName,
|
||||
currentApplication?.subdomain,
|
||||
currentApplication?.region?.awsName,
|
||||
'auth',
|
||||
);
|
||||
|
||||
|
||||
@@ -8,18 +8,18 @@ import Button from '@/ui/v2/Button';
|
||||
import Chip from '@/ui/v2/Chip';
|
||||
import { Dropdown } from '@/ui/v2/Dropdown';
|
||||
import IconButton from '@/ui/v2/IconButton';
|
||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||
import Input from '@/ui/v2/Input';
|
||||
import InputLabel from '@/ui/v2/InputLabel';
|
||||
import Option from '@/ui/v2/Option';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import CopyIcon from '@/ui/v2/icons/CopyIcon';
|
||||
import { copy } from '@/utils/copy';
|
||||
import getUserRoles from '@/utils/settings/getUserRoles';
|
||||
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
||||
import {
|
||||
useGetRolesQuery,
|
||||
useUpdateRemoteAppUserMutation,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { copy } from '@/utils/copy';
|
||||
import getUserRoles from '@/utils/settings/getUserRoles';
|
||||
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Avatar } from '@mui/material';
|
||||
import { format } from 'date-fns';
|
||||
@@ -137,7 +137,7 @@ export default function EditUserForm({
|
||||
}
|
||||
|
||||
const { data: dataRoles } = useGetRolesQuery({
|
||||
variables: { id: currentApplication.id },
|
||||
variables: { id: currentApplication?.id },
|
||||
});
|
||||
|
||||
const allAvailableProjectRoles = getUserRoles(
|
||||
@@ -206,11 +206,7 @@ export default function EditUserForm({
|
||||
</div>
|
||||
<div>
|
||||
<Dropdown.Root>
|
||||
<Dropdown.Trigger
|
||||
autoFocus={false}
|
||||
asChild
|
||||
className="gap-2"
|
||||
>
|
||||
<Dropdown.Trigger autoFocus={false} asChild className="gap-2">
|
||||
<Button variant="outlined" color="secondary">
|
||||
Actions
|
||||
</Button>
|
||||
|
||||
@@ -7,12 +7,14 @@ import Chip from '@/ui/v2/Chip';
|
||||
import Divider from '@/ui/v2/Divider';
|
||||
import { Dropdown } from '@/ui/v2/Dropdown';
|
||||
import IconButton from '@/ui/v2/IconButton';
|
||||
import List from '@/ui/v2/List';
|
||||
import { ListItem } from '@/ui/v2/ListItem';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import DotsHorizontalIcon from '@/ui/v2/icons/DotsHorizontalIcon';
|
||||
import TrashIcon from '@/ui/v2/icons/TrashIcon';
|
||||
import UserIcon from '@/ui/v2/icons/UserIcon';
|
||||
import List from '@/ui/v2/List';
|
||||
import { ListItem } from '@/ui/v2/ListItem';
|
||||
import Text from '@/ui/v2/Text';
|
||||
import getUserRoles from '@/utils/settings/getUserRoles';
|
||||
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
||||
import type { RemoteAppGetUsersQuery } from '@/utils/__generated__/graphql';
|
||||
import {
|
||||
useDeleteRemoteAppUserRolesMutation,
|
||||
@@ -21,9 +23,6 @@ import {
|
||||
useRemoteAppDeleteUserMutation,
|
||||
useUpdateRemoteAppUserMutation,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
|
||||
import getUserRoles from '@/utils/settings/getUserRoles';
|
||||
import { toastStyleProps } from '@/utils/settings/settingsConstants';
|
||||
import type { ApolloQueryResult } from '@apollo/client';
|
||||
import { Avatar } from '@mui/material';
|
||||
import { formatDistance } from 'date-fns';
|
||||
@@ -77,7 +76,7 @@ export default function UsersBody({
|
||||
* in the drawer form.
|
||||
*/
|
||||
const { data: dataRoles } = useGetRolesQuery({
|
||||
variables: { id: currentApplication.id },
|
||||
variables: { id: currentApplication?.id },
|
||||
});
|
||||
|
||||
const allAvailableProjectRoles = useMemo(
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
const terminalTheme = {
|
||||
hljs: {
|
||||
display: 'block',
|
||||
background: '#F4F7F9',
|
||||
color: '#21324B',
|
||||
},
|
||||
'hljs-tag': {
|
||||
color: '#9C73DF',
|
||||
},
|
||||
'hljs-keyword': {
|
||||
color: '#9C73DF',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-selector-tag': {
|
||||
color: '#9C73DF',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-literal': {
|
||||
color: '#9C73DF',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-strong': {
|
||||
color: '#9C73DF',
|
||||
},
|
||||
'hljs-name': {
|
||||
color: '#9C73DF',
|
||||
},
|
||||
'hljs-code': {
|
||||
color: '#66d9ef',
|
||||
},
|
||||
'hljs-class .hljs-title': {
|
||||
color: 'red',
|
||||
},
|
||||
'hljs-attribute': {
|
||||
color: '#bf79db',
|
||||
},
|
||||
'hljs-symbol': {
|
||||
color: '#bf79db',
|
||||
},
|
||||
'hljs-regexp': {
|
||||
color: '#bf79db',
|
||||
},
|
||||
'hljs-link': {
|
||||
color: '#bf79db',
|
||||
},
|
||||
'hljs-string': {
|
||||
color: '#B7A590',
|
||||
},
|
||||
'hljs-bullet': {
|
||||
color: '#B7A590',
|
||||
},
|
||||
'hljs-subst': {
|
||||
color: '#B7A590',
|
||||
},
|
||||
'hljs-title': {
|
||||
color: '#B7A590',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-section': {
|
||||
color: '#B7A590',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-emphasis': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-type': {
|
||||
color: '#3ECF8E',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-built_in': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-builtin-name': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-selector-attr': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-selector-pseudo': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-addition': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-variable': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-template-tag': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-template-variable': {
|
||||
color: '#3ECF8E',
|
||||
},
|
||||
'hljs-comment': {
|
||||
color: '#21324B',
|
||||
},
|
||||
'hljs-quote': {
|
||||
color: '#75715e',
|
||||
},
|
||||
'hljs-deletion': {
|
||||
color: '#75715e',
|
||||
},
|
||||
'hljs-meta': {
|
||||
color: '#75715e',
|
||||
},
|
||||
'hljs-doctag': {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
'hljs-selector-id': {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
};
|
||||
|
||||
export default terminalTheme;
|
||||
@@ -6,7 +6,6 @@ import FileTextIcon from '@/ui/v2/icons/FileTextIcon';
|
||||
import GraphQLIcon from '@/ui/v2/icons/GraphQLIcon';
|
||||
import HasuraIcon from '@/ui/v2/icons/HasuraIcon';
|
||||
import HomeIcon from '@/ui/v2/icons/HomeIcon';
|
||||
import LambdaIcon from '@/ui/v2/icons/LambdaIcon';
|
||||
import RocketIcon from '@/ui/v2/icons/RocketIcon';
|
||||
import StorageIcon from '@/ui/v2/icons/StorageIcon';
|
||||
import UserIcon from '@/ui/v2/icons/UserIcon';
|
||||
@@ -54,13 +53,6 @@ export default function useProjectRoutes() {
|
||||
const isPlatform = useIsPlatform();
|
||||
|
||||
const nhostRoutes: ProjectRoute[] = [
|
||||
{
|
||||
relativePath: '/functions',
|
||||
exact: false,
|
||||
label: 'Functions',
|
||||
icon: <LambdaIcon />,
|
||||
disabled: !isPlatform,
|
||||
},
|
||||
{
|
||||
relativePath: '/deployments',
|
||||
exact: false,
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
import ConnectGithubModal from '@/components/applications/ConnectGithubModal';
|
||||
import { FunctionsNotDeployed } from '@/components/applications/functions/FunctionsNotDeployed';
|
||||
import { normalizeFunctionMetadata } from '@/components/applications/functions/normalizeFunctionMetadata';
|
||||
import { EditRepositorySettings } from '@/components/applications/github/EditRepositorySettings';
|
||||
import useGitHubModal from '@/components/applications/github/useGitHubModal';
|
||||
import Folder from '@/components/icons/Folder';
|
||||
import Container from '@/components/layout/Container';
|
||||
import ProjectLayout from '@/components/layout/ProjectLayout';
|
||||
import { useWorkspaceContext } from '@/context/workspace-context';
|
||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||
import { Button } from '@/ui/Button';
|
||||
import DelayedLoading from '@/ui/DelayedLoading';
|
||||
import { Modal } from '@/ui/Modal';
|
||||
import Status, { StatusEnum } from '@/ui/Status';
|
||||
import { Text } from '@/ui/Text';
|
||||
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
||||
import { useGetAppFunctionsMetadataQuery } from '@/utils/__generated__/graphql';
|
||||
import { ChevronRightIcon } from '@heroicons/react/solid';
|
||||
import clsx from 'clsx';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
function FunctionsNoRepo() {
|
||||
const [githubModal, setGithubModal] = useState(false);
|
||||
const [githubRepoModal, setGithubRepoModal] = useState(false);
|
||||
const { openGitHubModal } = useGitHubModal();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal showModal={githubModal} close={() => setGithubModal(!githubModal)}>
|
||||
<ConnectGithubModal close={() => setGithubModal(false)} />
|
||||
</Modal>
|
||||
<Modal
|
||||
showModal={githubRepoModal}
|
||||
close={() => setGithubRepoModal(!githubRepoModal)}
|
||||
>
|
||||
<EditRepositorySettings
|
||||
openConnectGithubModal={() => setGithubModal(true)}
|
||||
close={() => setGithubRepoModal(false)}
|
||||
handleSelectAnotherRepository={openGitHubModal}
|
||||
/>
|
||||
</Modal>
|
||||
<div className="mx-auto flex w-centImage flex-col text-center">
|
||||
<Image
|
||||
src="/assets/githubRepo.svg"
|
||||
width={72}
|
||||
height={72}
|
||||
alt="GitHub Logo"
|
||||
/>
|
||||
</div>
|
||||
<Text className="mt-4 font-medium" size="large" color="dark">
|
||||
Function Logs
|
||||
</Text>
|
||||
<div className="flex">
|
||||
<div className="mx-auto flex flex-row self-center text-center">
|
||||
<Text size="normal" color="greyscaleDark" className="mt-1">
|
||||
To deploy serverless functions, you need to connect your project to
|
||||
version control.
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 flex text-center">
|
||||
<Button
|
||||
transparent
|
||||
color="blue"
|
||||
className="mx-auto font-medium"
|
||||
onClick={() => setGithubModal(true)}
|
||||
>
|
||||
Connect your Project to GitHub
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FunctionsPage() {
|
||||
const { currentWorkspace, currentApplication } =
|
||||
useCurrentWorkspaceAndApplication();
|
||||
const { workspaceContext } = useWorkspaceContext();
|
||||
|
||||
const { data, loading, error } = useGetAppFunctionsMetadataQuery({
|
||||
variables: { id: currentApplication?.id },
|
||||
});
|
||||
|
||||
const [normalizedFunctions, setNormalizedFunctions] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
if (data.app.metadataFunctions) {
|
||||
setNormalizedFunctions(
|
||||
normalizeFunctionMetadata(data.app.metadataFunctions),
|
||||
);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
if (!workspaceContext.repository) {
|
||||
return (
|
||||
<Container className="mt-12 max-w-3xl text-center antialiased">
|
||||
<FunctionsNoRepo />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Container>
|
||||
<DelayedLoading delay={500} className="mt-12" />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data || normalizedFunctions === null) {
|
||||
return (
|
||||
<Container>
|
||||
<FunctionsNotDeployed />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <Container>Error</Container>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<div className="mt-2">
|
||||
{normalizedFunctions?.map((folder) => (
|
||||
<div key={folder.folder}>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex flex-row pt-8 pb-2 align-middle',
|
||||
folder.nestedLevel < 2 && 'ml-6',
|
||||
folder.nestedLevel >= 2 && 'ml-12',
|
||||
)}
|
||||
>
|
||||
<div className={clsx('flex w-full')}>
|
||||
{folder.nestedLevel > 0 && (
|
||||
<Folder className="self-center align-middle text-greyscaleGrey" />
|
||||
)}
|
||||
<Text
|
||||
color="greyscaleDark"
|
||||
variant="body"
|
||||
className={clsx(
|
||||
'font-medium',
|
||||
folder.nestedLevel > 0 && 'ml-2',
|
||||
)}
|
||||
size="tiny"
|
||||
>
|
||||
{folder.folder}/
|
||||
</Text>
|
||||
</div>
|
||||
{folder.nestedLevel === 0 ? (
|
||||
<div className="flex w-full flex-row">
|
||||
<div className="flex w-52">
|
||||
<Text
|
||||
color="greyscaleDark"
|
||||
variant="body"
|
||||
className="font-medium"
|
||||
size="tiny"
|
||||
>
|
||||
Created At
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex w-16 self-end">
|
||||
<Text
|
||||
color="greyscaleDark"
|
||||
variant="body"
|
||||
className="font-medium"
|
||||
size="tiny"
|
||||
>
|
||||
Status
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'border-t py-1',
|
||||
folder.nestedLevel < 2 && 'ml-6',
|
||||
folder.nestedLevel >= 2 && 'ml-12',
|
||||
)}
|
||||
>
|
||||
{folder.funcs.map((func) => (
|
||||
<Link
|
||||
key={func.id}
|
||||
href={{
|
||||
pathname:
|
||||
'/[workspaceSlug]/[appSlug]/functions/[functionId]',
|
||||
query: {
|
||||
workspaceSlug: currentWorkspace.slug,
|
||||
appSlug: currentApplication.slug,
|
||||
functionId: func.functionName,
|
||||
},
|
||||
}}
|
||||
passHref
|
||||
>
|
||||
<a
|
||||
href="[workspaceSlug]/[appSlug]/functions/[functionId]"
|
||||
className={clsx(
|
||||
'flex cursor-pointer flex-row border-b py-2.5',
|
||||
folder.nestedLevel && 'ml-0',
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full flex-row items-center">
|
||||
<Image
|
||||
src={`/assets/functions/${func.lang}.svg`}
|
||||
alt={`Logo of ${func.lang}`}
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
|
||||
<Text
|
||||
color="greyscaleDark"
|
||||
variant="body"
|
||||
className="pl-2 font-medium"
|
||||
size="small"
|
||||
>
|
||||
{func.name}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex w-full flex-row">
|
||||
<div className={clsx('flex w-52 self-center')}>
|
||||
<Text
|
||||
color="greyscaleDark"
|
||||
variant="body"
|
||||
className=""
|
||||
size="tiny"
|
||||
>
|
||||
{func.formattedCreatedAt || '-'}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex w-16 self-center">
|
||||
<Status status={StatusEnum.Live}>Live</Status>
|
||||
|
||||
<ChevronRightIcon className="middl ml-2 h-4 w-4 cursor-pointer self-center" />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mx-auto mt-10 max-w-6xl">
|
||||
<div className="text-center">
|
||||
<Text size="tiny" color="greyscaleDark" className="font-medium">
|
||||
Base URL for function endpoints is{' '}
|
||||
{generateAppServiceUrl(
|
||||
currentApplication.subdomain,
|
||||
currentApplication.region.awsName,
|
||||
'functions',
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
FunctionsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return <ProjectLayout>{page}</ProjectLayout>;
|
||||
};
|
||||
@@ -1,142 +0,0 @@
|
||||
import { FunctionsLogsTerminalPage } from '@/components/applications/functions/FunctionLogsTerminalFromPage';
|
||||
import type { Func } from '@/components/applications/functions/normalizeFunctionMetadata';
|
||||
import { normalizeFunctionMetadata } from '@/components/applications/functions/normalizeFunctionMetadata';
|
||||
import { LoadingScreen } from '@/components/common/LoadingScreen';
|
||||
import Help from '@/components/icons/Help';
|
||||
import Container from '@/components/layout/Container';
|
||||
import ProjectLayout from '@/components/layout/ProjectLayout';
|
||||
import { useCurrentWorkspaceAndApplication } from '@/hooks/useCurrentWorkspaceAndApplication';
|
||||
import { useGetAllUserWorkspacesAndApplications } from '@/hooks/useGetAllUserWorkspacesAndApplications';
|
||||
import { Text } from '@/ui/Text';
|
||||
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
|
||||
import { yieldFunction } from '@/utils/helpers';
|
||||
import { useGetAppFunctionsMetadataQuery } from '@/utils/__generated__/graphql';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export default function FunctionDetailsPage() {
|
||||
const { currentApplication, currentWorkspace } =
|
||||
useCurrentWorkspaceAndApplication();
|
||||
useGetAllUserWorkspacesAndApplications(false);
|
||||
|
||||
const { data, loading, error } = useGetAppFunctionsMetadataQuery({
|
||||
variables: { id: currentApplication?.id },
|
||||
});
|
||||
|
||||
const [currentFunction, setCurrentFunction] = useState<Func | null>(null);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// currentFunction will be null until we get data back from remote and we set it to be the function we're looking for.
|
||||
useEffect(() => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
const appFunctions = normalizeFunctionMetadata(data?.app.metadataFunctions);
|
||||
setCurrentFunction(yieldFunction(appFunctions, router));
|
||||
}, [data, router]);
|
||||
|
||||
if (!currentApplication || !currentWorkspace || loading) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new Error(
|
||||
error.message ||
|
||||
'An unexpected error has ocurred. Please try again later.',
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentFunction) {
|
||||
return (
|
||||
<Container>
|
||||
<h1 className="text-4xl font-semibold text-greyscaleDark">Not found</h1>
|
||||
<p className="text-sm text-greyscaleGrey">
|
||||
This function does not exist.
|
||||
</p>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container>
|
||||
<div className="flex place-content-between">
|
||||
<div className="flex flex-row items-center py-1">
|
||||
<Image
|
||||
src={`/assets/functions/${
|
||||
currentFunction.name.split('.')[1]
|
||||
}.svg`}
|
||||
alt={`Logo of ${currentFunction.name.split('.')[1]}`}
|
||||
width={40}
|
||||
height={40}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col">
|
||||
<Text
|
||||
color="greyscaleDark"
|
||||
variant="body"
|
||||
className="ml-2 font-medium"
|
||||
size="big"
|
||||
>
|
||||
{currentFunction.name}
|
||||
</Text>
|
||||
<a
|
||||
className="ml-2 text-xs font-medium text-greyscaleGrey"
|
||||
href={`${generateAppServiceUrl(
|
||||
currentApplication.subdomain,
|
||||
currentApplication.region.awsName,
|
||||
'functions',
|
||||
)}${currentFunction?.route}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{`${generateAppServiceUrl(
|
||||
currentApplication.subdomain,
|
||||
currentApplication.region.awsName,
|
||||
'functions',
|
||||
)}${currentFunction?.route}`}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
<Container className="pt-10">
|
||||
<div className="flex flex-row place-content-between">
|
||||
<div className="flex">
|
||||
<Text size="large" className="font-medium" color="greyscaleDark">
|
||||
Log
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Text
|
||||
size="tiny"
|
||||
className="self-center font-medium"
|
||||
color="greyscaleDark"
|
||||
>
|
||||
Awaiting new requests…
|
||||
</Text>
|
||||
<a
|
||||
href="https://docs.nhost.io/platform/serverless-functions"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Help className="h-7 w-7" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-5">
|
||||
<FunctionsLogsTerminalPage functionName={currentFunction?.path} />
|
||||
</div>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
FunctionDetailsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return <ProjectLayout>{page}</ProjectLayout>;
|
||||
};
|
||||
@@ -1,10 +1,5 @@
|
||||
import type {
|
||||
FinalFunction,
|
||||
Func,
|
||||
} from '@/components/applications/functions/normalizeFunctionMetadata';
|
||||
import features from '@/data/features.json';
|
||||
import { ApplicationStatus } from '@/types/application';
|
||||
import type { NextRouter } from 'next/router';
|
||||
import slugify from 'slugify';
|
||||
import { LOCAL_BACKEND_URL } from './env';
|
||||
import type { DeploymentRowFragment } from './__generated__/graphql';
|
||||
@@ -89,26 +84,6 @@ export function emptyWorkspace() {
|
||||
};
|
||||
}
|
||||
|
||||
export function yieldFunction(
|
||||
functionsToSearch: FinalFunction[],
|
||||
router: NextRouter,
|
||||
): Func {
|
||||
let functionToReturn: Func = null;
|
||||
|
||||
functionsToSearch.forEach((currentFolder) => {
|
||||
currentFolder.funcs.forEach((currentFunction) => {
|
||||
if (
|
||||
!functionToReturn &&
|
||||
currentFunction.functionName === router.query.functionId
|
||||
) {
|
||||
functionToReturn = currentFunction;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return functionToReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the state number of the application to its string equivalent.
|
||||
* @param appStatus The current state of the application.
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/docs
|
||||
|
||||
## 0.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e6dad4d6: Added remote schemas
|
||||
|
||||
## 0.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
4
docs/docs/graphql/remote-schemas/_category_.json
Normal file
4
docs/docs/graphql/remote-schemas/_category_.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"label": "Remote Schemas",
|
||||
"position": 11
|
||||
}
|
||||
252
docs/docs/graphql/remote-schemas/stripe.mdx
Normal file
252
docs/docs/graphql/remote-schemas/stripe.mdx
Normal file
@@ -0,0 +1,252 @@
|
||||
---
|
||||
title: Stripe GraphQL API
|
||||
sidebar_label: Stripe
|
||||
sidebar_position: 2
|
||||
image: /img/og/graphql.png
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
This package creates a Stripe GraphQL API, allowing for interaction with data at Stripe.
|
||||
|
||||
Here's an example of how to use the Stripe GraphQL API to get a list of invoices for a specific Stripe customer:
|
||||
|
||||
<Tabs >
|
||||
<TabItem value="request" label="Request" default>
|
||||
|
||||
```graphql
|
||||
query {
|
||||
stripe {
|
||||
customer(id: "cus_xxx") {
|
||||
id
|
||||
name
|
||||
invoices {
|
||||
data {
|
||||
id
|
||||
created
|
||||
paid
|
||||
hostedInvoiceUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="response" label="Response">
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"stripe": {
|
||||
"customer": {
|
||||
"id": "cus_xxx",
|
||||
"name": "joe@example.com",
|
||||
"invoices": {
|
||||
"data": [
|
||||
{
|
||||
"id": "in_1MUmwnCCF9wuB4xxxxxxxx",
|
||||
"created": 1674806769,
|
||||
"paid": true,
|
||||
"hostedInvoiceUrl": "https://invoice.stripe.com/i/acct_xxxxxxx/test_YWNjdF8xS25xV1lDQ0Y5d3VCNGZYLF9ORkhWxxxxxxxxxxxx?s=ap"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
It's recommended to add the Stripe GraphQL API as a [Remote Schema in Hasura](https://hasura.io/docs/latest/remote-schemas/index/) and connect data from your database with data in Stripe. By doing so, it's possible to request data from your database and Stripe in a single GraphQL query.
|
||||
|
||||
Here's an example of how to use the Stripe GraphQL API to get a list of invoices for a specific Stripe customer. Note that the user data is fetched from your database and the Stripe customer data is fetched from Stripe:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
users {
|
||||
# User in your database
|
||||
id
|
||||
displayName
|
||||
userData {
|
||||
stripeCustomerId # Customer's Stripe Customer Id
|
||||
stripeCustomer {
|
||||
# Data from Stripe
|
||||
id
|
||||
name
|
||||
paymentMethods {
|
||||
id
|
||||
card {
|
||||
brand
|
||||
last4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Get Started
|
||||
|
||||
Install the package:
|
||||
|
||||
<Tabs groupId="package-manager">
|
||||
<TabItem value="npm" label="npm" default>
|
||||
|
||||
```bash
|
||||
npm install @nhost/stripe-graphql-js
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```bash
|
||||
yarn install @nhost/stripe-graphql-js
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="pnpm" label="pnpm">
|
||||
|
||||
```bash
|
||||
pnpm add @nhost/stripe-graphql-js
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Serverless Function
|
||||
|
||||
Create a new [Serverless Function](/serverless-functions): `functions/graphql/stripe.ts`:
|
||||
|
||||
```ts
|
||||
import { createStripeGraphQLServer } from '@nhost/stripe-graphql-js'
|
||||
|
||||
const server = createStripeGraphQLServer()
|
||||
|
||||
export default server
|
||||
```
|
||||
|
||||
> You can run the Stripe GraphQL API in any Node.js environment because it's built using [GraphQL Yoga](https://github.com/dotansimha/graphql-yoga).
|
||||
|
||||
## Stripe Secret Key
|
||||
|
||||
Add `STRIPE_SECRET_KEY` as an environment variable.
|
||||
|
||||
If you're using Nhost, add `STRIPE_SECRET_KEY` to `.env.development` like this:
|
||||
|
||||
```
|
||||
STRIPE_SECRET_KEY=sk_test_***
|
||||
```
|
||||
|
||||
And add the production key (`sk_live_***`) to [environment variables](/platform/environment-variables) in the Nhost dashboard.
|
||||
|
||||
Learn more about [Stripe API keys](https://stripe.com/docs/keys#obtain-api-keys).
|
||||
|
||||
## Start Nhost
|
||||
|
||||
```
|
||||
nhost up
|
||||
```
|
||||
|
||||
Learn more about the [Nhost CLI](/cli).
|
||||
|
||||
## Test
|
||||
|
||||
Test the Stripe GraphQL API in the browser:
|
||||
|
||||
[http://localhost:1337/v1/functions/graphql/stripe](http://localhost:1337/v1/functions/graphql/stripe)
|
||||
|
||||
## Remote Schema
|
||||
|
||||
Add the Stripe GraphQL API as a Remote Schema in Hasura.
|
||||
|
||||
**URL**
|
||||
|
||||
```
|
||||
{{NHOST_FUNCTIONS_URL}}/graphql/stripe
|
||||
```
|
||||
|
||||
**Headers**
|
||||
|
||||
```
|
||||
x-nhost-webhook-secret: NHOST_WEBHOOK_SECRET (From env var)
|
||||
```
|
||||
|
||||
> The `NHOST_WEBHOOK_SECRET` is used to verify that the request is coming from Nhost. The environment variable is a [system environment variable](/platform/environment-variables#system-environment-variables) and is always available.
|
||||
|
||||

|
||||
|
||||
## Permissions
|
||||
|
||||
Here's a minimal example without any custom permissions. Only requests using the `x-hasura-admin-secret` header will work:
|
||||
|
||||
```js
|
||||
const server = createStripeGraphQLServer()
|
||||
```
|
||||
|
||||
For more granular permissions, you can pass an `isAllowed` function to the `createStripeGraphQLServer`. The `isAllowed` function takes a `stripeCustomerId` and [`context`](#context) as parameters and runs every time the GraphQL server makes a request to Stripe to get or modify data for a specific Stripe customer.
|
||||
|
||||
Here is an example of an `isAllowed` function:
|
||||
|
||||
```ts
|
||||
import { createStripeGraphQLServer } from '@nhost/stripe-graphql-js'
|
||||
|
||||
const isAllowed = (stripeCustomerId: string, context: Context) => {
|
||||
const { isAdmin, userClaims } = context
|
||||
|
||||
// allow all requests if they have a valid `x-hasura-admin-secret`
|
||||
if (isAdmin) {
|
||||
return true
|
||||
}
|
||||
|
||||
// get user id
|
||||
const userId = userClaims['x-hasura-user-id']
|
||||
|
||||
// check if the user is signed in
|
||||
if (!userId) {
|
||||
return false
|
||||
}
|
||||
|
||||
// get more user information from the database
|
||||
const { user } = await gqlSDK.getUser({
|
||||
id: userId
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if the user is part of a workspace with the `stripeCustomerId`
|
||||
return user.workspaceMembers.some((workspaceMember) => {
|
||||
return workspaceMember.workspace.stripeCustomerId === stripeCustomerId
|
||||
})
|
||||
}
|
||||
|
||||
const server = createStripeGraphQLServer({ isAllowed })
|
||||
|
||||
export default server
|
||||
```
|
||||
|
||||
### Context
|
||||
|
||||
The `context` object contains:
|
||||
|
||||
- `userClaims` - verified JWT claims from the user's access token.
|
||||
- `isAdmin` - `true` if the request was made using a valid `x-hasura-admin-secret` header.
|
||||
- `request` - [Fetch API Request object](https://developer.mozilla.org/en-US/docs/Web/API/Request) that represents the incoming HTTP request in platform-independent way. It can be useful for accessing headers to authenticate a user
|
||||
- `query` - the DocumentNode that was parsed from the GraphQL query string
|
||||
- `operationName` - the operation name selected from the incoming query
|
||||
- `variables` - the variables that were defined in the query
|
||||
- `extensions` - the extensions that were received from the client
|
||||
|
||||
Read more about the [default context from GraphQL Yoga](https://www.the-guild.dev/graphql/yoga-server/docs/features/context#default-context).
|
||||
|
||||
## Source Code
|
||||
|
||||
The source code is available on [GitHub](https://github.com/nhost/nhost/tree/main/integrations/stripe-graphql-js).
|
||||
@@ -11,9 +11,9 @@ With Nhost, you can deploy Serverless Functions to execute custom code. Each Ser
|
||||
|
||||
Serverless functions can be used to handle [event triggers](/database/event-triggers), form submissions, integrations (e.g. Stripe, Slack, etc), and more.
|
||||
|
||||
## Creating a Serverless Function
|
||||
## Create a Serverless Function
|
||||
|
||||
Every `.js` (JavaScript) and `.ts` (TypeScript) file in the `functions/` folder of your Nhost project is its own Serverless Function.
|
||||
Every `.ts` (TypeScript) and `.js` (JavaScript) file in the `functions/` folder of your Nhost project is its own Serverless Function.
|
||||
|
||||
<Tabs groupId="language">
|
||||
<TabItem value="ts" label="TypeScript" default>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/docs",
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.11",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
|
||||
BIN
docs/static/img/graphql/remote-schemas/stripe/remote-schema.png
vendored
Normal file
BIN
docs/static/img/graphql/remote-schemas/stripe/remote-schema.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 227 KiB |
@@ -1,5 +1,12 @@
|
||||
# @nhost-examples/serverless-functions
|
||||
|
||||
## 0.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [e6dad4d6]
|
||||
- @nhost/stripe-graphql-js@1.0.0
|
||||
|
||||
## 0.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nhost-examples/serverless-functions",
|
||||
"private": true,
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.13"
|
||||
},
|
||||
"dependencies": {
|
||||
"@graphql-yoga/node": "^2.13.13",
|
||||
"@nhost/stripe-graphql-js": "^0.0.8",
|
||||
"@nhost/stripe-graphql-js": "^1.0.0",
|
||||
"@pothos/core": "^3.21.0",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"graphql": "15.7.2",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/stripe-graphql-js
|
||||
|
||||
## 1.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
- e6dad4d6: Added remote schemas
|
||||
|
||||
## 0.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<h1 align="center">@nhost/stripe-graphql-js</h1>
|
||||
<h2 align="center">Stripe GraphQL API</h2>
|
||||
<h1 align="center">Stripe GraphQL API</h1>
|
||||
<h2 align="center">@nhost/stripe-graphql-js</h2>
|
||||
|
||||
<p align="center">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/@nhost/stripe-graphql-js">
|
||||
@@ -9,179 +9,9 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
This package creates a Stripe GraphQL API.
|
||||
## Documentation
|
||||
|
||||
```graphql
|
||||
query {
|
||||
stripe {
|
||||
customer(id: "cus_xxx" {
|
||||
id
|
||||
name
|
||||
invoices {
|
||||
data {
|
||||
id
|
||||
created
|
||||
paid
|
||||
hostedInvoiceUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also add the Stripe GraphQL API as a Hasura Remote Schema and connect data from your database and Stripe. This allows you to request data from your database and Stripe in a single GraphQL query:
|
||||
|
||||
```graphql
|
||||
query {
|
||||
users {
|
||||
# User in your database
|
||||
id
|
||||
displayName
|
||||
userData {
|
||||
stripeCustomerId # Customer's Stripe Customer Id
|
||||
stripeCustomer {
|
||||
# Data from Stripe
|
||||
id
|
||||
name
|
||||
paymentMethods {
|
||||
id
|
||||
card {
|
||||
brand
|
||||
last4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @nhost/stripe-graphql-js
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Serverless Function Setup
|
||||
|
||||
Create a new [Serverless Function](https://docs.nhost.io/platform/serverless-functions) `functions/graphql/stripe.ts`:
|
||||
|
||||
```js
|
||||
import { createStripeGraphQLServer } from '@nhost/stripe-graphql-js'
|
||||
|
||||
const server = createStripeGraphQLServer()
|
||||
|
||||
export default server
|
||||
```
|
||||
|
||||
> You can run the Stripe GraphQL API in any JS environment because it's built using [GraphQL Yoga](https://github.com/dotansimha/graphql-yoga).
|
||||
|
||||
### Stripe Secret Key
|
||||
|
||||
Add `STRIPE_SECRET_KEY` as an environment variable. If you're using Nhost, add `STRIPE_SECRET_KEY` to `.env.development` like this:
|
||||
|
||||
```
|
||||
STRIPE_SECRET_KEY=sk_test_xxx
|
||||
```
|
||||
|
||||
Learn more about [Stripe API keys](https://stripe.com/docs/keys#obtain-api-keys).
|
||||
|
||||
### Start Nhost
|
||||
|
||||
```
|
||||
nhost up
|
||||
```
|
||||
|
||||
Learn more about the [Nhost CLI](https://docs.nhost.io/platform/cli).
|
||||
|
||||
### Test
|
||||
|
||||
Test the Stripe GraphQL API in the browser:
|
||||
|
||||
[http://localhost:1337/v1/functions/graphql/stripe](http://localhost:1337/v1/functions/graphql/stripe)
|
||||
|
||||
### Remote Schema
|
||||
|
||||
Add the Stripe GraphQL API as a Remote Schema in Hasura.
|
||||
|
||||
**URL**
|
||||
|
||||
```
|
||||
{{NHOST_BACKEND_URL}}/v1/functions/graphql/stripe
|
||||
```
|
||||
|
||||
**Headers**
|
||||
|
||||
```
|
||||
x-nhost-webhook-secret: NHOST_WEBHOOK_SECRET (from env var)
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Permissions
|
||||
|
||||
Here's a minimal example without any custom permissions. Only requests using the `x-hasura-admin-secret` header will work:
|
||||
|
||||
```js
|
||||
const server = createStripeGraphQLServer()
|
||||
```
|
||||
|
||||
For more granular permissions, you can pass an `isAllowed` function to the `createStripeGraphQLServer`. The `isAllowed` function takes a `stripeCustomerId` and [`context`](#context) as parameters and runs every time the GraphQL server makes a request to Stripe to get or modify data for a specific Stripe customer.
|
||||
|
||||
Here is an example of an `isAllowed` function:
|
||||
|
||||
```js
|
||||
|
||||
const isAllowed = (stripeCustomerId: string, context: Context) => {
|
||||
const { isAdmin, userClaims } = context
|
||||
|
||||
// allow requests if it has a valid `x-hasura-admin-secret`
|
||||
if (isAdmin) {
|
||||
return true
|
||||
}
|
||||
|
||||
// get user id
|
||||
const userId = userClaims['x-hasura-user-id']
|
||||
|
||||
// check if user is signed in
|
||||
if (!userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// get more user information from the database
|
||||
const { user } = await gqlSDK.getUser({
|
||||
id: userId,
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if the user is part of a workspace with the `stripeCustomerId`
|
||||
return user.workspaceMembers
|
||||
.some((workspaceMember) => {
|
||||
return workspaceMember.workspace.stripeCustomerId === stripeCustomerId;
|
||||
});
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Context
|
||||
|
||||
The `context` object contains:
|
||||
|
||||
- `userClaims` - verified JWT claims from the user's access token.
|
||||
- `isAdmin` - `true` if the request was made using a valid `x-hasura-admin-secret` header.
|
||||
- `request` - [Fetch API Request object](https://developer.mozilla.org/en-US/docs/Web/API/Request) that represents the incoming HTTP request in platform-independent way. It can be useful for accessing headers to authenticate a user
|
||||
- `query` - the DocumentNode that was parsed from the GraphQL query string
|
||||
- `operationName` - the operation name selected from the incoming query
|
||||
- `variables` - the variables that were defined in the query
|
||||
- `extensions` - the extensions that were received from the client
|
||||
|
||||
Read more about the [default context from GraphQL Yoga](https://www.the-guild.dev/graphql/yoga-server/docs/features/context#default-context).
|
||||
[https://docs.nhost.io/graphql/remote-schemas/stripe](https://docs.nhost.io/graphql/remote-schemas/stripe).
|
||||
|
||||
## Development
|
||||
|
||||
|
||||
@@ -15,4 +15,6 @@ const server = createStripeGraphQLServer({
|
||||
graphiql: true
|
||||
})
|
||||
|
||||
server.start()
|
||||
server.listen(4000, () => {
|
||||
console.info('Stripe GraphQL API server is running on http://localhost:4000')
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/stripe-graphql-js",
|
||||
"version": "0.0.8",
|
||||
"version": "1.0.0",
|
||||
"description": "Stripe GraphQL API",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
@@ -40,18 +40,18 @@
|
||||
"verify:fix": "run-p prettier:fix lint:fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@graphql-yoga/node": "^2.13.13",
|
||||
"@pothos/core": "^3.21.0",
|
||||
"graphql": "16.6.0",
|
||||
"graphql-scalars": "^1.18.0",
|
||||
"graphql-yoga": "^3.4.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"stripe": "^10.10.0"
|
||||
"stripe": "^11.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonwebtoken": "^9.0.0",
|
||||
"@types/node": "^18.11.9",
|
||||
"dotenv": "^16.0.3",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typescript": "^4.8.4"
|
||||
"typescript": "^4.7.4"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import Stripe from 'stripe'
|
||||
|
||||
import { GraphQLYogaError } from '@graphql-yoga/node'
|
||||
import { GraphQLError } from 'graphql'
|
||||
|
||||
import { builder } from '../builder'
|
||||
import { stripe } from '../utils'
|
||||
@@ -12,7 +12,7 @@ builder.objectType('Stripe', {
|
||||
resolve: async (_parent, _, context) => {
|
||||
const { isAdmin } = context
|
||||
|
||||
if (!isAdmin) throw new GraphQLYogaError('Not allowed')
|
||||
if (!isAdmin) throw new GraphQLError('Not allowed')
|
||||
|
||||
const connectedAccounts = await stripe.accounts.list()
|
||||
|
||||
@@ -29,7 +29,7 @@ builder.objectType('Stripe', {
|
||||
resolve: async (_parent, { id }, context) => {
|
||||
const { isAdmin } = context
|
||||
|
||||
if (!isAdmin) throw new GraphQLYogaError('Not allowed')
|
||||
if (!isAdmin) throw new GraphQLError('Not allowed')
|
||||
|
||||
const connectedAccount = await stripe.accounts.retrieve(id)
|
||||
|
||||
@@ -46,14 +46,14 @@ builder.objectType('Stripe', {
|
||||
resolve: async (_parent, { id }, context) => {
|
||||
const { isAllowed } = context
|
||||
|
||||
if (!await isAllowed(id, context)) {
|
||||
throw new GraphQLYogaError('Not allowed')
|
||||
if (!(await isAllowed(id, context))) {
|
||||
throw new GraphQLError('Not allowed')
|
||||
}
|
||||
|
||||
const customer = await stripe.customers.retrieve(id)
|
||||
|
||||
if (customer.deleted) {
|
||||
throw new GraphQLYogaError('The Stripe customer is deleted')
|
||||
throw new GraphQLError('The Stripe customer is deleted')
|
||||
}
|
||||
|
||||
return customer
|
||||
@@ -129,8 +129,8 @@ builder.objectType('StripeMutations', {
|
||||
resolve: async (_, { customer, configuration, locale, returnUrl }, context) => {
|
||||
const { isAllowed } = context
|
||||
|
||||
if (!await isAllowed(customer, context)) {
|
||||
throw new GraphQLYogaError('Not allowed')
|
||||
if (!(await isAllowed(customer, context))) {
|
||||
throw new GraphQLError('Not allowed')
|
||||
}
|
||||
|
||||
const session = await stripe.billingPortal.sessions.create({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createServer, YogaInitialContext } from '@graphql-yoga/node'
|
||||
import { createServer } from 'node:http'
|
||||
import { createYoga, YogaInitialContext } from 'graphql-yoga'
|
||||
|
||||
import { schema } from './schema'
|
||||
import { Context, CreateServerProps } from './types'
|
||||
@@ -45,12 +46,15 @@ const createStripeGraphQLServer = (params?: CreateServerProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
return createServer({
|
||||
const yoga = createYoga({
|
||||
cors,
|
||||
graphiql,
|
||||
context,
|
||||
schema
|
||||
schema,
|
||||
graphqlEndpoint: '*'
|
||||
})
|
||||
|
||||
return createServer(yoga)
|
||||
}
|
||||
|
||||
export { createStripeGraphQLServer, schema }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type Stripe from 'stripe'
|
||||
|
||||
import type { CORSOptions, YogaInitialContext } from '@graphql-yoga/node'
|
||||
import type { CORSOptions, YogaInitialContext } from 'graphql-yoga'
|
||||
|
||||
export type StripeGraphQLContext = {
|
||||
isAllowed: (stripeCustomerId: string, context: Context) => boolean | Promise<boolean>
|
||||
|
||||
@@ -8,7 +8,7 @@ if (!process.env.STRIPE_SECRET_KEY) {
|
||||
}
|
||||
|
||||
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
||||
apiVersion: '2022-08-01'
|
||||
apiVersion: '2022-11-15'
|
||||
})
|
||||
|
||||
export const getUserClaims = (req: Request): UserHasuraClaims | undefined => {
|
||||
|
||||
263
pnpm-lock.yaml
generated
263
pnpm-lock.yaml
generated
@@ -684,7 +684,7 @@ importers:
|
||||
examples/serverless-functions:
|
||||
specifiers:
|
||||
'@graphql-yoga/node': ^2.13.13
|
||||
'@nhost/stripe-graphql-js': ^0.0.8
|
||||
'@nhost/stripe-graphql-js': ^1.0.0
|
||||
'@pothos/core': ^3.21.0
|
||||
'@types/express': ^4.17.13
|
||||
cross-fetch: ^3.1.5
|
||||
@@ -886,30 +886,30 @@ importers:
|
||||
|
||||
integrations/stripe-graphql-js:
|
||||
specifiers:
|
||||
'@graphql-yoga/node': ^2.13.13
|
||||
'@pothos/core': ^3.21.0
|
||||
'@types/jsonwebtoken': ^9.0.0
|
||||
'@types/node': ^18.11.9
|
||||
dotenv: ^16.0.3
|
||||
graphql: 16.6.0
|
||||
graphql-scalars: ^1.18.0
|
||||
graphql-yoga: ^3.4.0
|
||||
jsonwebtoken: ^9.0.0
|
||||
stripe: ^10.10.0
|
||||
stripe: ^11.8.0
|
||||
ts-node-dev: ^2.0.0
|
||||
typescript: ^4.8.4
|
||||
typescript: ^4.7.4
|
||||
dependencies:
|
||||
'@graphql-yoga/node': 2.13.13_graphql@16.6.0
|
||||
'@pothos/core': 3.21.0_graphql@16.6.0
|
||||
graphql: 16.6.0
|
||||
graphql-scalars: 1.18.0_graphql@16.6.0
|
||||
graphql-yoga: 3.4.0_xfoe4adolgvm4tvnio5xigcr6e
|
||||
jsonwebtoken: 9.0.0
|
||||
stripe: 10.10.0
|
||||
stripe: 11.8.0
|
||||
devDependencies:
|
||||
'@types/jsonwebtoken': 9.0.0
|
||||
'@types/node': 18.11.9
|
||||
dotenv: 16.0.3
|
||||
ts-node-dev: 2.0.0_cbe7ovvae6zqfnmtgctpgpys54
|
||||
typescript: 4.8.4
|
||||
ts-node-dev: 2.0.0_vq46kxj6zfka4f6ijsosnft3hy
|
||||
typescript: 4.7.4
|
||||
|
||||
packages/docgen:
|
||||
specifiers:
|
||||
@@ -5911,6 +5911,13 @@ packages:
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/@envelop/core/3.0.4:
|
||||
resolution: {integrity: sha512-AybIZxQsDlFQTWHy6YtX/MSQPVuw+eOFtTW90JsHn6EbmcQnD6N3edQfSiTGjggPRHLoC0+0cuYXp2Ly2r3vrQ==}
|
||||
dependencies:
|
||||
'@envelop/types': 3.0.1
|
||||
tslib: 2.4.0
|
||||
dev: false
|
||||
|
||||
/@envelop/parser-cache/4.7.0_4hr55tbjlvoppd2sokdhrbpreq:
|
||||
resolution: {integrity: sha512-63NfXDcW/vGn4U6NFxaZ0JbYWAcJb9A6jhTvghsSz1ZS+Dny/ci8bVSgVmM1q+N56hPyGsVPuyI+rIc71mPU5g==}
|
||||
peerDependencies:
|
||||
@@ -5923,6 +5930,18 @@ packages:
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@envelop/parser-cache/5.0.4_a6sekiasy2tqr6d5gj7n2wtjli:
|
||||
resolution: {integrity: sha512-+kp6nzCVLYI2WQExQcE3FSy6n9ZGB5GYi+ntyjYdxaXU41U1f8RVwiLdyh0Ewn5D/s/zaLin09xkFKITVSAKDw==}
|
||||
peerDependencies:
|
||||
'@envelop/core': ^3.0.4
|
||||
graphql: ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
dependencies:
|
||||
'@envelop/core': 3.0.4
|
||||
graphql: 16.6.0
|
||||
lru-cache: 6.0.0
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@envelop/types/2.4.0_graphql@16.6.0:
|
||||
resolution: {integrity: sha512-pjxS98cDQBS84X29VcwzH3aJ/KiLCGwyMxuj7/5FkdiaCXAD1JEvKEj9LARWlFYj1bY43uII4+UptFebrhiIaw==}
|
||||
peerDependencies:
|
||||
@@ -5932,6 +5951,12 @@ packages:
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@envelop/types/3.0.1:
|
||||
resolution: {integrity: sha512-Ok62K1K+rlS+wQw77k8Pis8+1/h7+/9Wk5Fgcc2U6M5haEWsLFAHcHsk8rYlnJdEUl2Y3yJcCSOYbt1dyTaU5w==}
|
||||
dependencies:
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@envelop/validation-cache/4.7.0_4hr55tbjlvoppd2sokdhrbpreq:
|
||||
resolution: {integrity: sha512-PzL+GfWJRT+JjsJqZAIxHKEkvkM3hxkeytS5O0QLXT8kURNBV28r+Kdnn2RCF5+6ILhyGpiDb60vaquBi7g4lw==}
|
||||
peerDependencies:
|
||||
@@ -5944,6 +5969,18 @@ packages:
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@envelop/validation-cache/5.0.5_a6sekiasy2tqr6d5gj7n2wtjli:
|
||||
resolution: {integrity: sha512-69sq5H7hvxE+7VV60i0bgnOiV1PX9GEJHKrBrVvyEZAXqYojKO3DP9jnLGryiPgVaBjN5yw12ge0l0s2gXbolQ==}
|
||||
peerDependencies:
|
||||
'@envelop/core': ^3.0.4
|
||||
graphql: ^14.0.0 || ^15.0.0 || ^16.0.0
|
||||
dependencies:
|
||||
'@envelop/core': 3.0.4
|
||||
graphql: 16.6.0
|
||||
lru-cache: 6.0.0
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@esbuild/android-arm/0.16.10:
|
||||
resolution: {integrity: sha512-RmJjQTRrO6VwUWDrzTBLmV4OJZTarYsiepLGlF2rYTVB701hSorPywPGvP6d8HCuuRibyXa5JX4s3jN2kHEtjQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -6925,6 +6962,19 @@ packages:
|
||||
value-or-promise: 1.0.11
|
||||
dev: true
|
||||
|
||||
/@graphql-tools/executor/0.0.12_graphql@16.6.0:
|
||||
resolution: {integrity: sha512-bWpZcYRo81jDoTVONTnxS9dDHhEkNVjxzvFCH4CRpuyzD3uL+5w3MhtxIh24QyWm4LvQ4f+Bz3eMV2xU2I5+FA==}
|
||||
peerDependencies:
|
||||
graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
|
||||
dependencies:
|
||||
'@graphql-tools/utils': 9.1.4_graphql@16.6.0
|
||||
'@graphql-typed-document-node/core': 3.1.1_graphql@16.6.0
|
||||
'@repeaterjs/repeater': 3.0.4
|
||||
graphql: 16.6.0
|
||||
tslib: 2.4.1
|
||||
value-or-promise: 1.0.12
|
||||
dev: false
|
||||
|
||||
/@graphql-tools/git-loader/7.2.13_graphql@16.6.0:
|
||||
resolution: {integrity: sha512-PBAzZWXzKUL+VvlUQOjF++246G1O6TTMzvIlxaecgxvTSlnljEXJcDQlxqXhfFPITc5MP7He0N1UcZPBU/DE7Q==}
|
||||
peerDependencies:
|
||||
@@ -7364,7 +7414,15 @@ packages:
|
||||
dependencies:
|
||||
graphql: 16.6.0
|
||||
tslib: 2.4.1
|
||||
dev: true
|
||||
|
||||
/@graphql-tools/utils/9.1.4_graphql@16.6.0:
|
||||
resolution: {integrity: sha512-hgIeLt95h9nQgQuzbbdhuZmh+8WV7RZ/6GbTj6t3IU4Zd2zs9yYJ2jgW/krO587GMOY8zCwrjNOMzD40u3l7Vg==}
|
||||
peerDependencies:
|
||||
graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
|
||||
dependencies:
|
||||
graphql: 16.6.0
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@graphql-tools/wrap/8.5.1_graphql@16.6.0:
|
||||
resolution: {integrity: sha512-KpVVfha2wLSpE08YLX0jeo5nXPfDLASlxOqMlvfa/B4X8SOVmuLyN1L5YZ132tPLDF93uflwlHFnUO5ahpRNlA==}
|
||||
@@ -7430,6 +7488,15 @@ packages:
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@graphql-yoga/subscription/3.1.0:
|
||||
resolution: {integrity: sha512-Vc9lh8KzIHyS3n4jBlCbz7zCjcbtQnOBpsymcRvHhFr2cuH+knmRn0EmzimMQ58jQ8kxoRXXC3KJS3RIxSdPIg==}
|
||||
dependencies:
|
||||
'@graphql-yoga/typed-event-target': 1.0.0
|
||||
'@repeaterjs/repeater': 3.0.4
|
||||
'@whatwg-node/events': 0.0.2
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@graphql-yoga/typed-event-target/0.1.1:
|
||||
resolution: {integrity: sha512-l23kLKHKhfD7jmv4OUlzxMTihSqgIjGWCSb0KdlLkeiaF2jjuo8pRhX200hFTrtjRHGSYS1fx2lltK/xWci+vw==}
|
||||
dependencies:
|
||||
@@ -7437,6 +7504,13 @@ packages:
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@graphql-yoga/typed-event-target/1.0.0:
|
||||
resolution: {integrity: sha512-Mqni6AEvl3VbpMtKw+TIjc9qS9a8hKhiAjFtqX488yq5oJtj9TkNlFTIacAVS3vnPiswNsmDiQqvwUOcJgi1DA==}
|
||||
dependencies:
|
||||
'@repeaterjs/repeater': 3.0.4
|
||||
tslib: 2.4.1
|
||||
dev: false
|
||||
|
||||
/@grpc/grpc-js/1.7.3:
|
||||
resolution: {integrity: sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==}
|
||||
engines: {node: ^8.13.0 || >=10.10.0}
|
||||
@@ -11699,7 +11773,6 @@ packages:
|
||||
|
||||
/@types/node/18.11.9:
|
||||
resolution: {integrity: sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==}
|
||||
dev: true
|
||||
|
||||
/@types/normalize-package-data/2.4.1:
|
||||
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
|
||||
@@ -13223,6 +13296,10 @@ packages:
|
||||
'@xtuc/long': 4.2.2
|
||||
dev: true
|
||||
|
||||
/@whatwg-node/events/0.0.2:
|
||||
resolution: {integrity: sha512-WKj/lI4QjnLuPrim0cfO7i+HsDSXHxNv1y0CrJhdntuO3hxWZmnXCwNDnwOvry11OjRin6cgWNF+j/9Pn8TN4w==}
|
||||
dev: false
|
||||
|
||||
/@whatwg-node/fetch/0.2.6:
|
||||
resolution: {integrity: sha512-NhHiqeGcKjgqUZvJTZSou9qsFEPBBG1LPm2Npz0cmcPvukhhQfjX+p3quRx6b9AyjNPp1f73VB1z4ApHy9FcNg==}
|
||||
dependencies:
|
||||
@@ -13269,6 +13346,34 @@ packages:
|
||||
- encoding
|
||||
dev: true
|
||||
|
||||
/@whatwg-node/fetch/0.6.2:
|
||||
resolution: {integrity: sha512-fCUycF1W+bI6XzwJFnbdDuxIldfKM3w8+AzVCLGlucm0D+AQ8ZMm2j84hdcIhfV6ZdE4Y1HFVrHosAxdDZ+nPw==}
|
||||
dependencies:
|
||||
'@peculiar/webcrypto': 1.4.0
|
||||
abort-controller: 3.0.0
|
||||
busboy: 1.6.0
|
||||
form-data-encoder: 1.7.2
|
||||
formdata-node: 4.3.2
|
||||
node-fetch: 2.6.7
|
||||
undici: 5.12.0
|
||||
urlpattern-polyfill: 6.0.2
|
||||
web-streams-polyfill: 3.2.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/@whatwg-node/server/0.5.8_@types+node@18.11.9:
|
||||
resolution: {integrity: sha512-29f2Ijk663Hr6hF5GU5a8ELGQVbNMMDBWF1lTdpIKGyLrLJTKixarp6COEyEN5H9tGzIRUQar9Z76A+Jb9DyzQ==}
|
||||
peerDependencies:
|
||||
'@types/node': ^18.0.6
|
||||
dependencies:
|
||||
'@types/node': 18.11.9
|
||||
'@whatwg-node/fetch': 0.6.2
|
||||
tslib: 2.4.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/@wry/context/0.6.1:
|
||||
resolution: {integrity: sha512-LOmVnY1iTU2D8tv4Xf6MVMZZ+juIJ87Kt/plMijjN20NMAXGmH4u8bS1t0uT74cZ5gwpocYueV58YwyI8y+GKw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -14092,7 +14197,7 @@ packages:
|
||||
/axios/0.25.0_debug@4.3.4:
|
||||
resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==}
|
||||
dependencies:
|
||||
follow-redirects: 1.15.2
|
||||
follow-redirects: 1.15.2_debug@4.3.4
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
dev: true
|
||||
@@ -19529,6 +19634,19 @@ packages:
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
dev: false
|
||||
|
||||
/follow-redirects/1.15.2_debug@4.3.4:
|
||||
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
dev: true
|
||||
|
||||
/for-each/0.3.3:
|
||||
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
|
||||
@@ -20493,6 +20611,28 @@ packages:
|
||||
dependencies:
|
||||
graphql: 16.6.0
|
||||
|
||||
/graphql-yoga/3.4.0_xfoe4adolgvm4tvnio5xigcr6e:
|
||||
resolution: {integrity: sha512-Cjx60mmpoK1qL/sLdM285VdAOQyJBKLuC6oMZrfO8QleneNtu0nDOM6Efv5m0IrRYSONEMtIYA7eNr0u/cCBfg==}
|
||||
peerDependencies:
|
||||
graphql: ^15.2.0 || ^16.0.0
|
||||
dependencies:
|
||||
'@envelop/core': 3.0.4
|
||||
'@envelop/parser-cache': 5.0.4_a6sekiasy2tqr6d5gj7n2wtjli
|
||||
'@envelop/validation-cache': 5.0.5_a6sekiasy2tqr6d5gj7n2wtjli
|
||||
'@graphql-tools/executor': 0.0.12_graphql@16.6.0
|
||||
'@graphql-tools/schema': 9.0.4_graphql@16.6.0
|
||||
'@graphql-tools/utils': 9.1.1_graphql@16.6.0
|
||||
'@graphql-yoga/subscription': 3.1.0
|
||||
'@whatwg-node/fetch': 0.6.2
|
||||
'@whatwg-node/server': 0.5.8_@types+node@18.11.9
|
||||
dset: 3.1.2
|
||||
graphql: 16.6.0
|
||||
tslib: 2.4.1
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/graphql/16.6.0:
|
||||
resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==}
|
||||
engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
|
||||
@@ -28412,14 +28552,6 @@ packages:
|
||||
acorn: 8.8.1
|
||||
dev: true
|
||||
|
||||
/stripe/10.10.0:
|
||||
resolution: {integrity: sha512-CINNkuDEmF1rsHoTKS39iZ641NFONKZKllUioeQkRpz7qYPJayp7Xl/V30vbM2n5CTJo3X0FmZ2CychThfPTDA==}
|
||||
engines: {node: ^8.1 || >=10.*}
|
||||
dependencies:
|
||||
'@types/node': 18.11.17
|
||||
qs: 6.11.0
|
||||
dev: false
|
||||
|
||||
/stripe/10.17.0:
|
||||
resolution: {integrity: sha512-JHV2KoL+nMQRXu3m9ervCZZvi4DDCJfzHUE6CmtJxR9TmizyYfrVuhGvnsZLLnheby9Qrnf4Hq6iOEcejGwnGQ==}
|
||||
engines: {node: ^8.1 || >=10.*}
|
||||
@@ -28436,6 +28568,14 @@ packages:
|
||||
qs: 6.11.0
|
||||
dev: false
|
||||
|
||||
/stripe/11.8.0:
|
||||
resolution: {integrity: sha512-aGwrJDqYzpjQj0ejt7oN7BE7kUjZFxhUz/gDeyDCS7CBpZhDb26Eb6z9sS8KdbsbmuS8rkkn2lBY4koK7L1ZCw==}
|
||||
engines: {node: '>=12.*'}
|
||||
dependencies:
|
||||
'@types/node': 18.11.17
|
||||
qs: 6.11.0
|
||||
dev: false
|
||||
|
||||
/stubs/3.0.0:
|
||||
resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==}
|
||||
dev: false
|
||||
@@ -29224,7 +29364,7 @@ packages:
|
||||
code-block-writer: 11.0.3
|
||||
dev: true
|
||||
|
||||
/ts-node-dev/2.0.0_cbe7ovvae6zqfnmtgctpgpys54:
|
||||
/ts-node-dev/2.0.0_vq46kxj6zfka4f6ijsosnft3hy:
|
||||
resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
hasBin: true
|
||||
@@ -29243,9 +29383,9 @@ packages:
|
||||
rimraf: 2.7.1
|
||||
source-map-support: 0.5.21
|
||||
tree-kill: 1.2.2
|
||||
ts-node: 10.9.1_cbe7ovvae6zqfnmtgctpgpys54
|
||||
ts-node: 10.9.1_vq46kxj6zfka4f6ijsosnft3hy
|
||||
tsconfig: 7.0.0
|
||||
typescript: 4.8.4
|
||||
typescript: 4.7.4
|
||||
transitivePeerDependencies:
|
||||
- '@swc/core'
|
||||
- '@swc/wasm'
|
||||
@@ -29283,37 +29423,6 @@ packages:
|
||||
yn: 3.1.1
|
||||
dev: true
|
||||
|
||||
/ts-node/10.9.1_cbe7ovvae6zqfnmtgctpgpys54:
|
||||
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@swc/core': '>=1.2.50'
|
||||
'@swc/wasm': '>=1.2.50'
|
||||
'@types/node': '*'
|
||||
typescript: '>=2.7'
|
||||
peerDependenciesMeta:
|
||||
'@swc/core':
|
||||
optional: true
|
||||
'@swc/wasm':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
'@tsconfig/node10': 1.0.8
|
||||
'@tsconfig/node12': 1.0.9
|
||||
'@tsconfig/node14': 1.0.1
|
||||
'@tsconfig/node16': 1.0.2
|
||||
'@types/node': 18.11.9
|
||||
acorn: 8.7.1
|
||||
acorn-walk: 8.2.0
|
||||
arg: 4.1.3
|
||||
create-require: 1.1.1
|
||||
diff: 4.0.2
|
||||
make-error: 1.3.6
|
||||
typescript: 4.8.4
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
dev: true
|
||||
|
||||
/ts-node/10.9.1_kdoazjjbodcwagz6yx6a7ztgpu:
|
||||
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
|
||||
hasBin: true
|
||||
@@ -29437,6 +29546,37 @@ packages:
|
||||
yn: 3.1.1
|
||||
dev: true
|
||||
|
||||
/ts-node/10.9.1_vq46kxj6zfka4f6ijsosnft3hy:
|
||||
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@swc/core': '>=1.2.50'
|
||||
'@swc/wasm': '>=1.2.50'
|
||||
'@types/node': '*'
|
||||
typescript: '>=2.7'
|
||||
peerDependenciesMeta:
|
||||
'@swc/core':
|
||||
optional: true
|
||||
'@swc/wasm':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
'@tsconfig/node10': 1.0.8
|
||||
'@tsconfig/node12': 1.0.9
|
||||
'@tsconfig/node14': 1.0.1
|
||||
'@tsconfig/node16': 1.0.2
|
||||
'@types/node': 18.11.9
|
||||
acorn: 8.7.1
|
||||
acorn-walk: 8.2.0
|
||||
arg: 4.1.3
|
||||
create-require: 1.1.1
|
||||
diff: 4.0.2
|
||||
make-error: 1.3.6
|
||||
typescript: 4.7.4
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
dev: true
|
||||
|
||||
/ts-pnp/1.2.0_typescript@4.9.3:
|
||||
resolution: {integrity: sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -29732,6 +29872,12 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/typescript/4.7.4:
|
||||
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
|
||||
engines: {node: '>=4.2.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/typescript/4.8.3:
|
||||
resolution: {integrity: sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==}
|
||||
engines: {node: '>=4.2.0'}
|
||||
@@ -30217,6 +30363,12 @@ packages:
|
||||
querystring: 0.2.0
|
||||
dev: true
|
||||
|
||||
/urlpattern-polyfill/6.0.2:
|
||||
resolution: {integrity: sha512-5vZjFlH9ofROmuWmXM9yj2wljYKgWstGwe8YTyiqM7hVum/g9LyCizPZtb3UqsuppVwety9QJmfc42VggLpTgg==}
|
||||
dependencies:
|
||||
braces: 3.0.2
|
||||
dev: false
|
||||
|
||||
/urql/3.0.3_onqnqwb3ubg5opvemcqf7c2qhy:
|
||||
resolution: {integrity: sha512-aVUAMRLdc5AOk239DxgXt6ZxTl/fEmjr7oyU5OGo8uvpqu42FkeJErzd2qBzhAQ3DyusoZIbqbBLPlnKo/yy2A==}
|
||||
peerDependencies:
|
||||
@@ -30474,6 +30626,11 @@ packages:
|
||||
resolution: {integrity: sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
/value-or-promise/1.0.12:
|
||||
resolution: {integrity: sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/vary/1.1.2:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
Reference in New Issue
Block a user