Compare commits
5 Commits
@nhost/nho
...
storage@0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3dae655858 | ||
|
|
2aa269734b | ||
|
|
bc91836f83 | ||
|
|
6d8b243571 | ||
|
|
c9967b1a6d |
2
.github/workflows/dashboard_wf_release.yaml
vendored
2
.github/workflows/dashboard_wf_release.yaml
vendored
@@ -88,7 +88,7 @@ jobs:
|
|||||||
- name: Bump version in source code
|
- name: Bump version in source code
|
||||||
run: |
|
run: |
|
||||||
find cli -type f -exec sed -i 's/"nhost\/dashboard:[^"]*"/"nhost\/dashboard:${{ inputs.VERSION }}"/g' {} +
|
find cli -type f -exec sed -i 's/"nhost\/dashboard:[^"]*"/"nhost\/dashboard:${{ inputs.VERSION }}"/g' {} +
|
||||||
sed -i 's/"nhost\/dashboard:[^"]*"/"nhost\/dashboard:${{ inputs.VERSION }}"/g' docs/reference/cli/commands.mdx
|
sed -i 's/nhost\/dashboard:[^)]*/nhost\/dashboard:${{ inputs.VERSION }}/g' docs/reference/cli/commands.mdx
|
||||||
|
|
||||||
- name: "Create Pull Request"
|
- name: "Create Pull Request"
|
||||||
uses: peter-evans/create-pull-request@v7
|
uses: peter-evans/create-pull-request@v7
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
## [@nhost/dashboard@2.41.0] - 2025-11-04
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- *(auth)* Added endpoints to retrieve and refresh oauth2 providers' tokens (#3614)
|
||||||
|
- *(dashboard)* Get github repositories from github itself (#3640)
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- *(dashboard)* Update SQL editor to use correct hasura migrations API URL (#3645)
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ function getCspHeader() {
|
|||||||
return [
|
return [
|
||||||
"default-src 'self' *.nhost.run wss://*.nhost.run nhost.run wss://nhost.run",
|
"default-src 'self' *.nhost.run wss://*.nhost.run nhost.run wss://nhost.run",
|
||||||
"script-src 'self' 'unsafe-eval' cdn.segment.com js.stripe.com challenges.cloudflare.com googletagmanager.com",
|
"script-src 'self' 'unsafe-eval' cdn.segment.com js.stripe.com challenges.cloudflare.com googletagmanager.com",
|
||||||
"connect-src 'self' *.nhost.run wss://*.nhost.run nhost.run wss://nhost.run discord.com api.segment.io api.segment.com cdn.segment.com nhost.zendesk.com",
|
"connect-src 'self' *.nhost.run wss://*.nhost.run nhost.run wss://nhost.run discord.com api.segment.io api.segment.com cdn.segment.com nhost.zendesk.com api.github.com",
|
||||||
"style-src 'self' 'unsafe-inline'",
|
"style-src 'self' 'unsafe-inline'",
|
||||||
"img-src 'self' blob: data: github.com avatars.githubusercontent.com s.gravatar.com *.nhost.run nhost.run",
|
"img-src 'self' blob: data: github.com avatars.githubusercontent.com s.gravatar.com *.nhost.run nhost.run",
|
||||||
"font-src 'self' data:",
|
"font-src 'self' data:",
|
||||||
@@ -126,4 +126,4 @@ module.exports = withBundleAnalyzer({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -23,7 +23,7 @@ export default function SocialProvidersSettings() {
|
|||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
return nhost.auth.signInProviderURL('github', {
|
return nhost.auth.signInProviderURL('github', {
|
||||||
connect: token,
|
connect: token,
|
||||||
redirectTo: `${window.location.origin}/account`,
|
redirectTo: `${window.location.origin}/account?signinProvider=github`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -3,18 +3,21 @@ import {
|
|||||||
useGithubAuthentication,
|
useGithubAuthentication,
|
||||||
type UseGithubAuthenticationHookProps,
|
type UseGithubAuthenticationHookProps,
|
||||||
} from '@/features/auth/AuthProviders/Github/hooks/useGithubAuthentication';
|
} from '@/features/auth/AuthProviders/Github/hooks/useGithubAuthentication';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
import { SiGithub } from '@icons-pack/react-simple-icons';
|
import { SiGithub } from '@icons-pack/react-simple-icons';
|
||||||
|
|
||||||
interface Props extends UseGithubAuthenticationHookProps {
|
interface Props extends UseGithubAuthenticationHookProps {
|
||||||
buttonText?: string;
|
buttonText?: string;
|
||||||
withAnonId?: boolean;
|
withAnonId?: boolean;
|
||||||
redirectTo?: string;
|
redirectTo?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function GithubAuthButton({
|
function GithubAuthButton({
|
||||||
buttonText = 'Continue with GitHub',
|
buttonText = 'Continue with GitHub',
|
||||||
withAnonId = false,
|
withAnonId = false,
|
||||||
redirectTo,
|
redirectTo,
|
||||||
|
className,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { mutate: signInWithGithub, isLoading } = useGithubAuthentication({
|
const { mutate: signInWithGithub, isLoading } = useGithubAuthentication({
|
||||||
withAnonId,
|
withAnonId,
|
||||||
@@ -22,7 +25,10 @@ function GithubAuthButton({
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className="gap-2 !bg-white text-sm+ !text-black hover:ring-2 hover:ring-white hover:ring-opacity-50 disabled:!text-black disabled:!text-opacity-60"
|
className={cn(
|
||||||
|
'gap-2 !bg-white text-sm+ !text-black hover:ring-2 hover:ring-white hover:ring-opacity-50 disabled:!text-black disabled:!text-opacity-60',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
onClick={() => signInWithGithub()}
|
onClick={() => signInWithGithub()}
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ function useGithubAuthentication({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const redirectURl = nhost.auth.signInProviderURL('github', options);
|
const redirectURL = nhost.auth.signInProviderURL('github', options);
|
||||||
window.location.href = redirectURl;
|
window.location.href = redirectURL;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onError: () => {
|
onError: () => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { GithubAuthButton } from '@/features/auth/AuthProviders/Github/component
|
|||||||
import { useHostName } from '@/features/orgs/projects/common/hooks/useHostName';
|
import { useHostName } from '@/features/orgs/projects/common/hooks/useHostName';
|
||||||
|
|
||||||
function SignInWithGithub() {
|
function SignInWithGithub() {
|
||||||
const redirectTo = useHostName();
|
const redirectTo = `${useHostName()}?signinProvider=github`;
|
||||||
return (
|
return (
|
||||||
<GithubAuthButton
|
<GithubAuthButton
|
||||||
redirectTo={redirectTo}
|
redirectTo={redirectTo}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { GithubAuthButton } from '@/features/auth/AuthProviders/Github/components/GithubAuthButton';
|
import { GithubAuthButton } from '@/features/auth/AuthProviders/Github/components/GithubAuthButton';
|
||||||
|
import { useHostName } from '@/features/orgs/projects/common/hooks/useHostName';
|
||||||
|
|
||||||
function SignUpWithGithub() {
|
function SignUpWithGithub() {
|
||||||
|
const redirectTo = `${useHostName()}?signinProvider=github`;
|
||||||
return (
|
return (
|
||||||
<GithubAuthButton
|
<GithubAuthButton
|
||||||
|
redirectTo={redirectTo}
|
||||||
buttonText="Sign Up with GitHub"
|
buttonText="Sign Up with GitHub"
|
||||||
errorText="An error occurred while trying to sign up using GitHub. Please try again."
|
errorText="An error occurred while trying to sign up using GitHub. Please try again."
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ErrorMessage } from '@/components/presentational/ErrorMessage';
|
||||||
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
|
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Avatar } from '@/components/ui/v2/Avatar';
|
import { Avatar } from '@/components/ui/v2/Avatar';
|
||||||
@@ -11,14 +12,33 @@ import { Link } from '@/components/ui/v2/Link';
|
|||||||
import { List } from '@/components/ui/v2/List';
|
import { List } from '@/components/ui/v2/List';
|
||||||
import { ListItem } from '@/components/ui/v2/ListItem';
|
import { ListItem } from '@/components/ui/v2/ListItem';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
import { GithubAuthButton } from '@/features/auth/AuthProviders/Github/components/GithubAuthButton';
|
||||||
|
import { useHostName } from '@/features/orgs/projects/common/hooks/useHostName';
|
||||||
import { EditRepositorySettings } from '@/features/orgs/projects/git/common/components/EditRepositorySettings';
|
import { EditRepositorySettings } from '@/features/orgs/projects/git/common/components/EditRepositorySettings';
|
||||||
import { useGetGithubRepositoriesQuery } from '@/generated/graphql';
|
import {
|
||||||
|
getGitHubToken,
|
||||||
|
saveGitHubToken,
|
||||||
|
} from '@/features/orgs/projects/git/common/utils';
|
||||||
|
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
|
||||||
|
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||||
|
import { useGetAuthUserProvidersQuery } from '@/generated/graphql';
|
||||||
|
import { useAccessToken } from '@/hooks/useAccessToken';
|
||||||
|
import { GitHubAPIError, listGitHubInstallationRepos } from '@/lib/github';
|
||||||
|
import { isEmptyValue } from '@/lib/utils';
|
||||||
|
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||||
|
import { nhost } from '@/utils/nhost';
|
||||||
import { Divider } from '@mui/material';
|
import { Divider } from '@mui/material';
|
||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
|
import NavLink from 'next/link';
|
||||||
import type { ChangeEvent } from 'react';
|
import type { ChangeEvent } from 'react';
|
||||||
import { Fragment, useEffect, useMemo, useState } from 'react';
|
import { Fragment, useEffect, useMemo, useState } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export type ConnectGitHubModalState = 'CONNECTING' | 'EDITING';
|
export type ConnectGitHubModalState =
|
||||||
|
| 'CONNECTING'
|
||||||
|
| 'EDITING'
|
||||||
|
| 'EXPIRED_GITHUB_SESSION'
|
||||||
|
| 'GITHUB_CONNECTION_REQUIRED';
|
||||||
|
|
||||||
export interface ConnectGitHubModalProps {
|
export interface ConnectGitHubModalProps {
|
||||||
/**
|
/**
|
||||||
@@ -28,18 +48,153 @@ export interface ConnectGitHubModalProps {
|
|||||||
close?: VoidFunction;
|
close?: VoidFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GitHubData {
|
||||||
|
githubAppInstallations: Array<{
|
||||||
|
id: number;
|
||||||
|
accountLogin?: string;
|
||||||
|
accountAvatarUrl?: string;
|
||||||
|
}>;
|
||||||
|
githubRepositories: Array<{
|
||||||
|
id: number;
|
||||||
|
node_id: string;
|
||||||
|
name: string;
|
||||||
|
fullName: string;
|
||||||
|
githubAppInstallation: {
|
||||||
|
accountLogin?: string;
|
||||||
|
accountAvatarUrl?: string;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
|
export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
|
||||||
const [filter, setFilter] = useState('');
|
const [filter, setFilter] = useState('');
|
||||||
const [ConnectGitHubModalState, setConnectGitHubModalState] =
|
const [ConnectGitHubModalState, setConnectGitHubModalState] =
|
||||||
useState<ConnectGitHubModalState>('CONNECTING');
|
useState<ConnectGitHubModalState>('CONNECTING');
|
||||||
const [selectedRepoId, setSelectedRepoId] = useState<string | null>(null);
|
const [selectedRepoId, setSelectedRepoId] = useState<string | null>(null);
|
||||||
|
const [githubData, setGithubData] = useState<GitHubData | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const { project, loading: loadingProject } = useProject();
|
||||||
|
const { org, loading: loadingOrg } = useCurrentOrg();
|
||||||
|
const hostname = useHostName();
|
||||||
|
const token = useAccessToken();
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
loading: loadingGithubConnected,
|
||||||
|
error: errorGithubConnected,
|
||||||
|
} = useGetAuthUserProvidersQuery();
|
||||||
|
|
||||||
const { data, loading, error, startPolling } =
|
const githubProvider = data?.authUserProviders?.find(
|
||||||
useGetGithubRepositoriesQuery();
|
(item) => item.providerId === 'github',
|
||||||
|
);
|
||||||
|
|
||||||
|
const getGitHubConnectUrl = () => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
return nhost.auth.signInProviderURL('github', {
|
||||||
|
connect: token,
|
||||||
|
redirectTo: `${window.location.origin}?signinProvider=github&state=signin-refresh:${org.slug}:${project?.subdomain}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
startPolling(2000);
|
if (loadingGithubConnected) {
|
||||||
}, [startPolling]);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchGitHubData = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
if (isEmptyValue(githubProvider)) {
|
||||||
|
setConnectGitHubModalState('GITHUB_CONNECTION_REQUIRED');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const githubToken = getGitHubToken();
|
||||||
|
|
||||||
|
if (
|
||||||
|
!githubToken?.authUserProviderId ||
|
||||||
|
githubProvider!.id !== githubToken.authUserProviderId
|
||||||
|
) {
|
||||||
|
setConnectGitHubModalState('EXPIRED_GITHUB_SESSION');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { refreshToken, expiresAt: expiresAtString } = githubToken;
|
||||||
|
let accessToken = githubToken?.accessToken;
|
||||||
|
|
||||||
|
const expiresAt = new Date(expiresAtString).getTime();
|
||||||
|
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const expiresAtMargin = 60 * 1000;
|
||||||
|
if (expiresAt - currentTime < expiresAtMargin) {
|
||||||
|
if (!refreshToken) {
|
||||||
|
setConnectGitHubModalState('EXPIRED_GITHUB_SESSION');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshResponse = await nhost.auth.refreshProviderToken(
|
||||||
|
'github',
|
||||||
|
{ refreshToken },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!refreshResponse.body) {
|
||||||
|
setConnectGitHubModalState('EXPIRED_GITHUB_SESSION');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveGitHubToken({
|
||||||
|
...refreshResponse.body,
|
||||||
|
authUserProviderId: githubProvider!.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
accessToken = refreshResponse.body.accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
const installations = await listGitHubInstallationRepos(accessToken);
|
||||||
|
|
||||||
|
const transformedData = {
|
||||||
|
githubAppInstallations: installations.map((item) => ({
|
||||||
|
id: item.installation.id,
|
||||||
|
accountLogin: item.installation.account?.login,
|
||||||
|
accountAvatarUrl: item.installation.account?.avatar_url,
|
||||||
|
})),
|
||||||
|
githubRepositories: installations.flatMap((item) =>
|
||||||
|
item.repositories.map((repo) => ({
|
||||||
|
id: repo.id,
|
||||||
|
node_id: repo.node_id,
|
||||||
|
name: repo.name,
|
||||||
|
fullName: repo.full_name,
|
||||||
|
githubAppInstallation: {
|
||||||
|
accountLogin: item.installation.account?.login,
|
||||||
|
accountAvatarUrl: item.installation.account?.avatar_url,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
setGithubData(transformedData);
|
||||||
|
setLoading(false);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching GitHub data:', err);
|
||||||
|
if (err instanceof GitHubAPIError && err.status === 401) {
|
||||||
|
setConnectGitHubModalState('EXPIRED_GITHUB_SESSION');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.error(err?.message, getToastStyleProps());
|
||||||
|
close?.();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchGitHubData();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [githubProvider, loadingGithubConnected]);
|
||||||
|
|
||||||
const handleSelectAnotherRepository = () => {
|
const handleSelectAnotherRepository = () => {
|
||||||
setSelectedRepoId(null);
|
setSelectedRepoId(null);
|
||||||
@@ -56,13 +211,91 @@ export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
|
|||||||
|
|
||||||
useEffect(() => () => handleFilterChange.cancel(), [handleFilterChange]);
|
useEffect(() => () => handleFilterChange.cancel(), [handleFilterChange]);
|
||||||
|
|
||||||
if (error) {
|
if (errorGithubConnected instanceof Error) {
|
||||||
throw error;
|
return (
|
||||||
|
<div className="px-1 md:w-[653px]">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="mx-auto text-center">
|
||||||
|
<div className="mx-auto h-8 w-8">
|
||||||
|
<GitHubIcon className="h-8 w-8" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Text className="mt-2.5 text-center text-lg font-medium">
|
||||||
|
Error fetching GitHub data
|
||||||
|
</Text>
|
||||||
|
<ErrorMessage>{errorGithubConnected.message}</ErrorMessage>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading || loadingProject || loadingOrg || loadingGithubConnected) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator delay={500} label="Loading GitHub repositories..." />
|
<div className="px-1 md:w-[653px]">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="mx-auto text-center">
|
||||||
|
<div className="mx-auto h-8 w-8">
|
||||||
|
<GitHubIcon className="h-8 w-8" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text className="mt-2.5 text-center text-lg font-medium">
|
||||||
|
Loading repositories...
|
||||||
|
</Text>
|
||||||
|
<Text className="text-center text-xs font-normal" color="secondary">
|
||||||
|
Fetching your GitHub repositories
|
||||||
|
</Text>
|
||||||
|
<div className="mb-2 mt-6 flex w-full">
|
||||||
|
<Input placeholder="Search..." fullWidth disabled value="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex h-import items-center justify-center border-y">
|
||||||
|
<ActivityIndicator delay={0} label="" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ConnectGitHubModalState === 'GITHUB_CONNECTION_REQUIRED') {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center gap-5 px-1 py-1 md:w-[653px]">
|
||||||
|
<p className="text-center text-foreground">
|
||||||
|
You need to connect your GitHub account to continue.
|
||||||
|
</p>
|
||||||
|
<NavLink
|
||||||
|
href={getGitHubConnectUrl()}
|
||||||
|
passHref
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
legacyBehavior
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="w-full max-w-72"
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
startIcon={<GitHubIcon />}
|
||||||
|
>
|
||||||
|
Connect to GitHub
|
||||||
|
</Button>
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ConnectGitHubModalState === 'EXPIRED_GITHUB_SESSION') {
|
||||||
|
return (
|
||||||
|
<div className="flex w-full flex-col items-center justify-center gap-5 px-1 py-1 md:w-[653px]">
|
||||||
|
<p className="text-center text-foreground">
|
||||||
|
Please sign in with GitHub to continue.
|
||||||
|
</p>
|
||||||
|
<GithubAuthButton
|
||||||
|
redirectTo={`${hostname}?signinProvider=github&state=signin-refresh:${org.slug}:${project!.subdomain}`}
|
||||||
|
buttonText="Sign in with GitHub"
|
||||||
|
className="w-full max-w-72 gap-2 !bg-primary !text-white disabled:!text-white disabled:!text-opacity-60 dark:!bg-white dark:!text-black dark:disabled:!text-black"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,25 +311,27 @@ export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { githubAppInstallations } = data || {};
|
const { githubAppInstallations } = githubData || {};
|
||||||
|
|
||||||
const filteredGitHubAppInstallations = data?.githubAppInstallations.filter(
|
const filteredGitHubAppInstallations =
|
||||||
(githubApp) => !!githubApp.accountLogin,
|
githubData?.githubAppInstallations.filter(
|
||||||
);
|
(githubApp) => !!githubApp.accountLogin,
|
||||||
|
);
|
||||||
|
|
||||||
const filteredGitHubRepositories = data?.githubRepositories.filter(
|
const filteredGitHubRepositories = githubData?.githubRepositories.filter(
|
||||||
(repo) => !!repo.githubAppInstallation,
|
(repo) => !!repo.githubAppInstallation,
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredGitHubAppInstallationsNullValues =
|
const filteredGitHubAppInstallationsNullValues =
|
||||||
data?.githubAppInstallations.filter((githubApp) => !!githubApp.accountLogin)
|
githubData?.githubAppInstallations.filter(
|
||||||
.length === 0;
|
(githubApp) => !!githubApp.accountLogin,
|
||||||
|
).length === 0;
|
||||||
|
|
||||||
const faultyGitHubInstallation =
|
const faultyGitHubInstallation =
|
||||||
githubAppInstallations?.length === 0 ||
|
githubAppInstallations?.length === 0 ||
|
||||||
filteredGitHubAppInstallationsNullValues;
|
filteredGitHubAppInstallationsNullValues;
|
||||||
|
|
||||||
const noRepositoriesAdded = data?.githubRepositories.length === 0;
|
const noRepositoriesAdded = githubData?.githubRepositories.length === 0;
|
||||||
|
|
||||||
if (faultyGitHubInstallation) {
|
if (faultyGitHubInstallation) {
|
||||||
return (
|
return (
|
||||||
@@ -115,11 +350,7 @@ export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
href={process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL}
|
href={`${process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL}?state=install-github-app:${org.slug}:${project!.subdomain}`}
|
||||||
// Both `target` and `rel` are available when `href` is set. This is
|
|
||||||
// a limitation of MUI.
|
|
||||||
// @ts-ignore
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
endIcon={<ArrowSquareOutIcon className="h-4 w-4" />}
|
endIcon={<ArrowSquareOutIcon className="h-4 w-4" />}
|
||||||
>
|
>
|
||||||
@@ -179,8 +410,7 @@ export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
|
|||||||
</List>
|
</List>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
href={process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL}
|
href={`${process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL}?state=install-github-app:${org.slug}:${project!.subdomain}`}
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
underline="hover"
|
underline="hover"
|
||||||
className="grid grid-flow-col items-center justify-start gap-1"
|
className="grid grid-flow-col items-center justify-start gap-1"
|
||||||
@@ -199,8 +429,8 @@ export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
|
|||||||
className="text-center text-xs font-normal"
|
className="text-center text-xs font-normal"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
>
|
>
|
||||||
Showing repositories from {data?.githubAppInstallations.length}{' '}
|
Showing repositories from{' '}
|
||||||
GitHub account(s)
|
{githubData?.githubAppInstallations.length} GitHub account(s)
|
||||||
</Text>
|
</Text>
|
||||||
<div className="mb-2 mt-6 flex w-full">
|
<div className="mb-2 mt-6 flex w-full">
|
||||||
<Input
|
<Input
|
||||||
@@ -226,7 +456,7 @@ export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
|
|||||||
<Button
|
<Button
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => setSelectedRepoId(repo.id)}
|
onClick={() => setSelectedRepoId(repo.node_id)}
|
||||||
>
|
>
|
||||||
Connect
|
Connect
|
||||||
</Button>
|
</Button>
|
||||||
@@ -268,8 +498,7 @@ export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
|
|||||||
Do you miss a repository, or do you need to connect another GitHub
|
Do you miss a repository, or do you need to connect another GitHub
|
||||||
account?{' '}
|
account?{' '}
|
||||||
<Link
|
<Link
|
||||||
href={process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL}
|
href={`${process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL}?state=install-github-app:${org.slug}:${project!.subdomain}`}
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
className="text-xs font-medium"
|
className="text-xs font-medium"
|
||||||
underline="hover"
|
underline="hover"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { FormProvider, useForm } from 'react-hook-form';
|
|||||||
export interface EditRepositorySettingsProps {
|
export interface EditRepositorySettingsProps {
|
||||||
close?: () => void;
|
close?: () => void;
|
||||||
openConnectGithubModal?: () => void;
|
openConnectGithubModal?: () => void;
|
||||||
selectedRepoId?: string;
|
selectedRepoId: string;
|
||||||
connectGithubModalState?: ConnectGitHubModalState;
|
connectGithubModalState?: ConnectGitHubModalState;
|
||||||
handleSelectAnotherRepository?: () => void;
|
handleSelectAnotherRepository?: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import { Text } from '@/components/ui/v2/Text';
|
|||||||
import { EditRepositoryAndBranchSettings } from '@/features/orgs/projects/git/common/components/EditRepositoryAndBranchSettings';
|
import { EditRepositoryAndBranchSettings } from '@/features/orgs/projects/git/common/components/EditRepositoryAndBranchSettings';
|
||||||
import type { EditRepositorySettingsFormData } from '@/features/orgs/projects/git/common/components/EditRepositorySettings';
|
import type { EditRepositorySettingsFormData } from '@/features/orgs/projects/git/common/components/EditRepositorySettings';
|
||||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||||
import { useUpdateApplicationMutation } from '@/generated/graphql';
|
import { useConnectGithubRepoMutation } from '@/generated/graphql';
|
||||||
import { analytics } from '@/lib/segment';
|
import { analytics } from '@/lib/segment';
|
||||||
import { discordAnnounce } from '@/utils/discordAnnounce';
|
import { discordAnnounce } from '@/utils/discordAnnounce';
|
||||||
import { triggerToast } from '@/utils/toast';
|
import { triggerToast } from '@/utils/toast';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
export interface EditRepositorySettingsModalProps {
|
export interface EditRepositorySettingsModalProps {
|
||||||
selectedRepoId?: string;
|
selectedRepoId: string;
|
||||||
close?: () => void;
|
close?: () => void;
|
||||||
handleSelectAnotherRepository?: () => void;
|
handleSelectAnotherRepository?: () => void;
|
||||||
}
|
}
|
||||||
@@ -33,45 +33,29 @@ export default function EditRepositorySettingsModal({
|
|||||||
|
|
||||||
const { project, refetch: refetchProject } = useProject();
|
const { project, refetch: refetchProject } = useProject();
|
||||||
|
|
||||||
const [updateApp, { loading }] = useUpdateApplicationMutation();
|
const [connectGithubRepo, { loading }] = useConnectGithubRepoMutation();
|
||||||
|
|
||||||
const handleEditGitHubIntegration = async (
|
const handleEditGitHubIntegration = async (
|
||||||
data: EditRepositorySettingsFormData,
|
data: EditRepositorySettingsFormData,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
if (!project?.githubRepository || selectedRepoId) {
|
await connectGithubRepo({
|
||||||
await updateApp({
|
variables: {
|
||||||
variables: {
|
appID: project?.id,
|
||||||
appId: project?.id,
|
githubNodeID: selectedRepoId,
|
||||||
app: {
|
productionBranch: data.productionBranch,
|
||||||
githubRepositoryId: selectedRepoId,
|
baseFolder: data.repoBaseFolder,
|
||||||
repositoryProductionBranch: data.productionBranch,
|
},
|
||||||
nhostBaseFolder: data.repoBaseFolder,
|
});
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (selectedRepoId) {
|
analytics.track('Project Connected to GitHub', {
|
||||||
analytics.track('Project Connected to GitHub', {
|
projectId: project?.id,
|
||||||
projectId: project?.id,
|
projectName: project?.name,
|
||||||
projectName: project?.name,
|
projectSubdomain: project?.subdomain,
|
||||||
projectSubdomain: project?.subdomain,
|
repositoryId: selectedRepoId,
|
||||||
repositoryId: selectedRepoId,
|
productionBranch: data.productionBranch,
|
||||||
productionBranch: data.productionBranch,
|
baseFolder: data.repoBaseFolder,
|
||||||
baseFolder: data.repoBaseFolder,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await updateApp({
|
|
||||||
variables: {
|
|
||||||
appId: project.id,
|
|
||||||
app: {
|
|
||||||
repositoryProductionBranch: data.productionBranch,
|
|
||||||
nhostBaseFolder: data.repoBaseFolder,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await refetchProject();
|
await refetchProject();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
mutation ConnectGithubRepo(
|
||||||
|
$appID: uuid!
|
||||||
|
$githubNodeID: String!
|
||||||
|
$productionBranch: String!
|
||||||
|
$baseFolder: String!
|
||||||
|
) {
|
||||||
|
connectGithubRepo(
|
||||||
|
appID: $appID
|
||||||
|
githubNodeID: $githubNodeID
|
||||||
|
productionBranch: $productionBranch
|
||||||
|
baseFolder: $baseFolder
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,12 +2,12 @@ import { useDialog } from '@/components/common/DialogProvider';
|
|||||||
import { ConnectGitHubModal } from '@/features/orgs/projects/git/common/components/ConnectGitHubModal';
|
import { ConnectGitHubModal } from '@/features/orgs/projects/git/common/components/ConnectGitHubModal';
|
||||||
|
|
||||||
export default function useGitHubModal() {
|
export default function useGitHubModal() {
|
||||||
const { openAlertDialog } = useDialog();
|
const { openAlertDialog, closeAlertDialog } = useDialog();
|
||||||
|
|
||||||
function openGitHubModal() {
|
function openGitHubModal() {
|
||||||
openAlertDialog({
|
openAlertDialog({
|
||||||
title: 'Connect GitHub Repository',
|
title: 'Connect GitHub Repository',
|
||||||
payload: <ConnectGitHubModal />,
|
payload: <ConnectGitHubModal close={closeAlertDialog} />,
|
||||||
props: {
|
props: {
|
||||||
hidePrimaryAction: true,
|
hidePrimaryAction: true,
|
||||||
hideSecondaryAction: true,
|
hideSecondaryAction: true,
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { isNotEmptyValue } from '@/lib/utils';
|
||||||
|
import type { ProviderSession } from '@nhost/nhost-js/auth';
|
||||||
|
|
||||||
|
const githubProviderTokenKey = 'nhost_provider_tokens_github';
|
||||||
|
|
||||||
|
export type GitHubProviderToken = ProviderSession & {
|
||||||
|
authUserProviderId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function saveGitHubToken(token: GitHubProviderToken) {
|
||||||
|
localStorage.setItem(githubProviderTokenKey, JSON.stringify(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getGitHubToken() {
|
||||||
|
const token = localStorage.getItem(githubProviderTokenKey);
|
||||||
|
return isNotEmptyValue(token)
|
||||||
|
? (JSON.parse(token) as GitHubProviderToken)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearGitHubToken() {
|
||||||
|
localStorage.removeItem(githubProviderTokenKey);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './githubTokens';
|
||||||
99
dashboard/src/lib/github.ts
Normal file
99
dashboard/src/lib/github.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* Custom error class for GitHub API errors that preserves HTTP status codes
|
||||||
|
*/
|
||||||
|
export class GitHubAPIError extends Error {
|
||||||
|
constructor(
|
||||||
|
message: string,
|
||||||
|
public status: number,
|
||||||
|
public statusText: string,
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'GitHubAPIError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GitHubAppInstallation {
|
||||||
|
id: number;
|
||||||
|
account?: {
|
||||||
|
login: string;
|
||||||
|
avatar_url: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists all GitHub App installations accessible to the user
|
||||||
|
* @param accessToken - The GitHub OAuth access token
|
||||||
|
* @returns Array of app installations
|
||||||
|
*/
|
||||||
|
export async function listGitHubAppInstallations(accessToken: string): Promise<GitHubAppInstallation[]> {
|
||||||
|
const response = await fetch('https://api.github.com/user/installations', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
Accept: 'application/vnd.github+json',
|
||||||
|
'X-GitHub-Api-Version': '2022-11-28',
|
||||||
|
},
|
||||||
|
cache: 'no-cache',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new GitHubAPIError(
|
||||||
|
`Failed to list installations: ${response.statusText}`,
|
||||||
|
response.status,
|
||||||
|
response.statusText
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.installations;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GitHubRepo {
|
||||||
|
id: number;
|
||||||
|
node_id: string;
|
||||||
|
name: string;
|
||||||
|
full_name: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists all repositories accessible through GitHub App installations
|
||||||
|
* @param accessToken - The GitHub OAuth access token
|
||||||
|
* @returns Array of repositories grouped by installation
|
||||||
|
*/
|
||||||
|
export async function listGitHubInstallationRepos(accessToken: string) {
|
||||||
|
const installations = await listGitHubAppInstallations(accessToken);
|
||||||
|
|
||||||
|
const reposByInstallation = await Promise.all(
|
||||||
|
installations.map(async (installation) => {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.github.com/user/installations/${installation.id}/repositories`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
Accept: 'application/vnd.github+json',
|
||||||
|
'X-GitHub-Api-Version': '2022-11-28',
|
||||||
|
},
|
||||||
|
cache: 'no-cache',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new GitHubAPIError(
|
||||||
|
`Failed to list repos for installation ${installation.id}: ${response.statusText}`,
|
||||||
|
response.status,
|
||||||
|
response.statusText
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return {
|
||||||
|
installation,
|
||||||
|
repositories: data.repositories as GitHubRepo[],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return reposByInstallation;
|
||||||
|
}
|
||||||
@@ -77,12 +77,12 @@ function MyApp({
|
|||||||
|
|
||||||
<CacheProvider value={emotionCache}>
|
<CacheProvider value={emotionCache}>
|
||||||
<NhostProvider nhost={nhost}>
|
<NhostProvider nhost={nhost}>
|
||||||
<AuthProvider>
|
<NhostApolloProvider
|
||||||
<NhostApolloProvider
|
fetchPolicy="cache-and-network"
|
||||||
fetchPolicy="cache-and-network"
|
nhost={nhost}
|
||||||
nhost={nhost}
|
connectToDevTools={process.env.NEXT_PUBLIC_ENV === 'dev'}
|
||||||
connectToDevTools={process.env.NEXT_PUBLIC_ENV === 'dev'}
|
>
|
||||||
>
|
<AuthProvider>
|
||||||
<UIProvider>
|
<UIProvider>
|
||||||
<Toaster position="bottom-center" />
|
<Toaster position="bottom-center" />
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
@@ -106,8 +106,8 @@ function MyApp({
|
|||||||
</RetryableErrorBoundary>
|
</RetryableErrorBoundary>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</UIProvider>
|
</UIProvider>
|
||||||
</NhostApolloProvider>
|
</AuthProvider>
|
||||||
</AuthProvider>
|
</NhostApolloProvider>
|
||||||
</NhostProvider>
|
</NhostProvider>
|
||||||
</CacheProvider>
|
</CacheProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
import { LoadingScreen } from '@/components/presentational/LoadingScreen';
|
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
|
||||||
import { nhost } from '@/utils/nhost';
|
|
||||||
|
|
||||||
import { useAuth } from '@/providers/Auth';
|
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import type { ComponentType } from 'react';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
export function authProtected<P extends JSX.IntrinsicAttributes>(
|
|
||||||
Comp: ComponentType<P>,
|
|
||||||
) {
|
|
||||||
return function AuthProtected(props: P) {
|
|
||||||
const router = useRouter();
|
|
||||||
const { isAuthenticated, isLoading } = useAuth();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isLoading || isAuthenticated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
router.push('/signin');
|
|
||||||
}, [isLoading, isAuthenticated, router]);
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <LoadingScreen />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Comp {...props} />;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function Page() {
|
|
||||||
const [state, setState] = useState({
|
|
||||||
error: null,
|
|
||||||
loading: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { installation_id: installationId } = router.query;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function installGithubApp() {
|
|
||||||
try {
|
|
||||||
await nhost.functions.fetch('/client/github-app-installation', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
installationId,
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
setState({
|
|
||||||
error,
|
|
||||||
loading: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState({
|
|
||||||
error: null,
|
|
||||||
loading: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
window.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// run in async manner
|
|
||||||
installGithubApp();
|
|
||||||
}, [installationId]);
|
|
||||||
|
|
||||||
if (state.loading) {
|
|
||||||
return <ActivityIndicator delay={500} label="Loading..." />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.error) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
|
||||||
throw state.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div>GitHub connection completed. You can close this tab.</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default authProtected(Page);
|
|
||||||
@@ -1,12 +1,38 @@
|
|||||||
import { Container } from '@/components/layout/Container';
|
import { Container } from '@/components/layout/Container';
|
||||||
import { OrgLayout } from '@/features/orgs/layout/OrgLayout';
|
import { OrgLayout } from '@/features/orgs/layout/OrgLayout';
|
||||||
import { SettingsLayout } from '@/features/orgs/layout/SettingsLayout';
|
import { SettingsLayout } from '@/features/orgs/layout/SettingsLayout';
|
||||||
|
import { useGitHubModal } from '@/features/orgs/projects/git/common/hooks/useGitHubModal';
|
||||||
import { BaseDirectorySettings } from '@/features/orgs/projects/git/settings/components/BaseDirectorySettings';
|
import { BaseDirectorySettings } from '@/features/orgs/projects/git/settings/components/BaseDirectorySettings';
|
||||||
import { DeploymentBranchSettings } from '@/features/orgs/projects/git/settings/components/DeploymentBranchSettings';
|
import { DeploymentBranchSettings } from '@/features/orgs/projects/git/settings/components/DeploymentBranchSettings';
|
||||||
import { GitConnectionSettings } from '@/features/orgs/projects/git/settings/components/GitConnectionSettings';
|
import { GitConnectionSettings } from '@/features/orgs/projects/git/settings/components/GitConnectionSettings';
|
||||||
import type { ReactElement } from 'react';
|
import { useRouter } from 'next/router';
|
||||||
|
import { useCallback, useEffect, type ReactElement } from 'react';
|
||||||
|
|
||||||
export default function GitSettingsPage() {
|
export default function GitSettingsPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const { pathname, replace, isReady: isRouterReady } = router;
|
||||||
|
const { 'github-modal': githubModal, ...remainingQuery } = router.query;
|
||||||
|
const { openGitHubModal } = useGitHubModal();
|
||||||
|
|
||||||
|
const removeQueryParamsFromURL = useCallback(() => {
|
||||||
|
replace({ pathname, query: remainingQuery }, undefined, {
|
||||||
|
shallow: true,
|
||||||
|
});
|
||||||
|
}, [replace, remainingQuery, pathname]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isRouterReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof githubModal === 'string') {
|
||||||
|
removeQueryParamsFromURL();
|
||||||
|
|
||||||
|
openGitHubModal();
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [githubModal, isRouterReady]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent"
|
className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent"
|
||||||
|
|||||||
@@ -1,19 +1,27 @@
|
|||||||
|
import {
|
||||||
|
clearGitHubToken,
|
||||||
|
saveGitHubToken,
|
||||||
|
type GitHubProviderToken,
|
||||||
|
} from '@/features/orgs/projects/git/common/utils';
|
||||||
|
import { isNotEmptyValue } from '@/lib/utils';
|
||||||
import { useNhostClient } from '@/providers/nhost/';
|
import { useNhostClient } from '@/providers/nhost/';
|
||||||
|
import { useGetAuthUserProvidersLazyQuery } from '@/utils/__generated__/graphql';
|
||||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||||
import { type Session } from '@nhost/nhost-js/auth';
|
import { type Session } from '@nhost/nhost-js/auth';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import {
|
import {
|
||||||
type PropsWithChildren,
|
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
|
type PropsWithChildren,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import { AuthContext, type AuthContextType } from './AuthContext';
|
import { AuthContext, type AuthContextType } from './AuthContext';
|
||||||
|
|
||||||
function AuthProvider({ children }: PropsWithChildren) {
|
function AuthProvider({ children }: PropsWithChildren) {
|
||||||
const nhost = useNhostClient();
|
const nhost = useNhostClient();
|
||||||
|
const [getAuthUserProviders] = useGetAuthUserProvidersLazyQuery();
|
||||||
const {
|
const {
|
||||||
query,
|
query,
|
||||||
isReady: isRouterReady,
|
isReady: isRouterReady,
|
||||||
@@ -21,7 +29,15 @@ function AuthProvider({ children }: PropsWithChildren) {
|
|||||||
pathname,
|
pathname,
|
||||||
push,
|
push,
|
||||||
} = useRouter();
|
} = useRouter();
|
||||||
const { refreshToken, error, errorDescription, ...remainingQuery } = query;
|
const {
|
||||||
|
refreshToken,
|
||||||
|
error,
|
||||||
|
errorDescription,
|
||||||
|
signinProvider,
|
||||||
|
state,
|
||||||
|
provider_state: providerState,
|
||||||
|
...remainingQuery
|
||||||
|
} = query;
|
||||||
const [session, setSession] = useState<Session | null>(null);
|
const [session, setSession] = useState<Session | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isSigningOut, setIsSigningOut] = useState(false);
|
const [isSigningOut, setIsSigningOut] = useState(false);
|
||||||
@@ -55,7 +71,6 @@ function AuthProvider({ children }: PropsWithChildren) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
// reset state if we have just signed out
|
|
||||||
setIsSigningOut(false);
|
setIsSigningOut(false);
|
||||||
if (refreshToken && typeof refreshToken === 'string') {
|
if (refreshToken && typeof refreshToken === 'string') {
|
||||||
const sessionResponse = await nhost.auth.refreshToken({
|
const sessionResponse = await nhost.auth.refreshToken({
|
||||||
@@ -63,24 +78,84 @@ function AuthProvider({ children }: PropsWithChildren) {
|
|||||||
});
|
});
|
||||||
setSession(sessionResponse.body);
|
setSession(sessionResponse.body);
|
||||||
removeQueryParamsFromURL();
|
removeQueryParamsFromURL();
|
||||||
|
|
||||||
|
if (sessionResponse.body && signinProvider === 'github') {
|
||||||
|
try {
|
||||||
|
const providerTokensResponse =
|
||||||
|
await nhost.auth.getProviderTokens(signinProvider);
|
||||||
|
if (providerTokensResponse.body) {
|
||||||
|
const { data } = await getAuthUserProviders();
|
||||||
|
const githubProvider = data?.authUserProviders?.find(
|
||||||
|
(provider) => provider.providerId === 'github',
|
||||||
|
);
|
||||||
|
const newGitHubToken: GitHubProviderToken =
|
||||||
|
providerTokensResponse.body;
|
||||||
|
if (isNotEmptyValue(githubProvider?.id)) {
|
||||||
|
newGitHubToken.authUserProviderId = githubProvider!.id;
|
||||||
|
}
|
||||||
|
saveGitHubToken(newGitHubToken);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch provider tokens:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const currentSession = nhost.getUserSession();
|
const currentSession = nhost.getUserSession();
|
||||||
setSession(currentSession);
|
setSession(currentSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle OAuth redirect errors (e.g., error=unverified-user)
|
if (
|
||||||
|
state &&
|
||||||
|
typeof state === 'string' &&
|
||||||
|
state.startsWith('signin-refresh:')
|
||||||
|
) {
|
||||||
|
const [, orgSlug, projectSubdomain] = state.split(':');
|
||||||
|
removeQueryParamsFromURL();
|
||||||
|
await push(
|
||||||
|
`/orgs/${orgSlug}/projects/${projectSubdomain}/settings/git?github-modal`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof error === 'string') {
|
if (typeof error === 'string') {
|
||||||
if (error === 'unverified-user') {
|
switch (error) {
|
||||||
removeQueryParamsFromURL();
|
case 'unverified-user': {
|
||||||
await push('/email/verify');
|
removeQueryParamsFromURL();
|
||||||
} else {
|
await push('/email/verify');
|
||||||
const description =
|
break;
|
||||||
typeof errorDescription === 'string'
|
}
|
||||||
? errorDescription
|
|
||||||
: 'An error occurred during the sign-in process. Please try again.';
|
/*
|
||||||
toast.error(description, getToastStyleProps());
|
* If the state isn't handled by Hasura auth, it returns `invalid-state`.
|
||||||
removeQueryParamsFromURL();
|
* However, we check the provider_state search param to see if it has this format:
|
||||||
await push('/signin');
|
* `install-github-app:<org-slug>:<project-subdomain>`.
|
||||||
|
* If it has this format, that means that we connected to GitHub in `/settings/git`,
|
||||||
|
* thus we need to show the connect GitHub modal again.
|
||||||
|
* Otherwise, we fall through to default error handling.
|
||||||
|
*/
|
||||||
|
case 'invalid-state': {
|
||||||
|
if (
|
||||||
|
isNotEmptyValue(providerState) &&
|
||||||
|
typeof providerState === 'string' &&
|
||||||
|
providerState.startsWith('install-github-app:')
|
||||||
|
) {
|
||||||
|
const [, orgSlug, projectSubdomain] = providerState.split(':');
|
||||||
|
removeQueryParamsFromURL();
|
||||||
|
await push(
|
||||||
|
`/orgs/${orgSlug}/projects/${projectSubdomain}/settings/git?github-modal`,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Fall through to default error handling if state search param is invalid
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
const description =
|
||||||
|
typeof errorDescription === 'string'
|
||||||
|
? errorDescription
|
||||||
|
: 'An error occurred during the sign-in process. Please try again.';
|
||||||
|
toast.error(description, getToastStyleProps());
|
||||||
|
removeQueryParamsFromURL();
|
||||||
|
await push('/signin');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +178,7 @@ function AuthProvider({ children }: PropsWithChildren) {
|
|||||||
nhost.auth.signOut({
|
nhost.auth.signOut({
|
||||||
refreshToken: session!.refreshToken,
|
refreshToken: session!.refreshToken,
|
||||||
});
|
});
|
||||||
|
clearGitHubToken();
|
||||||
|
|
||||||
await push('/signin');
|
await push('/signin');
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -108,16 +108,16 @@ function Providers({ children }: PropsWithChildren<{}>) {
|
|||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<CacheProvider value={emotionCache}>
|
<CacheProvider value={emotionCache}>
|
||||||
<NhostProvider nhost={nhost}>
|
<NhostProvider nhost={nhost}>
|
||||||
<AuthProvider>
|
<ApolloProvider client={mockClient}>
|
||||||
<ApolloProvider client={mockClient}>
|
<AuthProvider>
|
||||||
<UIProvider>
|
<UIProvider>
|
||||||
<Toaster position="bottom-center" />
|
<Toaster position="bottom-center" />
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<DialogProvider>{children}</DialogProvider>
|
<DialogProvider>{children}</DialogProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</UIProvider>
|
</UIProvider>
|
||||||
</ApolloProvider>
|
</AuthProvider>
|
||||||
</AuthProvider>
|
</ApolloProvider>
|
||||||
</NhostProvider>
|
</NhostProvider>
|
||||||
</CacheProvider>
|
</CacheProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
|||||||
82
dashboard/src/utils/__generated__/graphql.ts
generated
82
dashboard/src/utils/__generated__/graphql.ts
generated
@@ -3118,7 +3118,9 @@ export type ConfigSystemConfigPostgres = {
|
|||||||
database: Scalars['String'];
|
database: Scalars['String'];
|
||||||
disk?: Maybe<ConfigSystemConfigPostgresDisk>;
|
disk?: Maybe<ConfigSystemConfigPostgresDisk>;
|
||||||
enabled?: Maybe<Scalars['Boolean']>;
|
enabled?: Maybe<Scalars['Boolean']>;
|
||||||
|
encryptColumnKey?: Maybe<Scalars['String']>;
|
||||||
majorVersion?: Maybe<Scalars['String']>;
|
majorVersion?: Maybe<Scalars['String']>;
|
||||||
|
oldEncryptColumnKey?: Maybe<Scalars['String']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConfigSystemConfigPostgresComparisonExp = {
|
export type ConfigSystemConfigPostgresComparisonExp = {
|
||||||
@@ -3129,7 +3131,9 @@ export type ConfigSystemConfigPostgresComparisonExp = {
|
|||||||
database?: InputMaybe<ConfigStringComparisonExp>;
|
database?: InputMaybe<ConfigStringComparisonExp>;
|
||||||
disk?: InputMaybe<ConfigSystemConfigPostgresDiskComparisonExp>;
|
disk?: InputMaybe<ConfigSystemConfigPostgresDiskComparisonExp>;
|
||||||
enabled?: InputMaybe<ConfigBooleanComparisonExp>;
|
enabled?: InputMaybe<ConfigBooleanComparisonExp>;
|
||||||
|
encryptColumnKey?: InputMaybe<ConfigStringComparisonExp>;
|
||||||
majorVersion?: InputMaybe<ConfigStringComparisonExp>;
|
majorVersion?: InputMaybe<ConfigStringComparisonExp>;
|
||||||
|
oldEncryptColumnKey?: InputMaybe<ConfigStringComparisonExp>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConfigSystemConfigPostgresConnectionString = {
|
export type ConfigSystemConfigPostgresConnectionString = {
|
||||||
@@ -3193,7 +3197,9 @@ export type ConfigSystemConfigPostgresInsertInput = {
|
|||||||
database: Scalars['String'];
|
database: Scalars['String'];
|
||||||
disk?: InputMaybe<ConfigSystemConfigPostgresDiskInsertInput>;
|
disk?: InputMaybe<ConfigSystemConfigPostgresDiskInsertInput>;
|
||||||
enabled?: InputMaybe<Scalars['Boolean']>;
|
enabled?: InputMaybe<Scalars['Boolean']>;
|
||||||
|
encryptColumnKey?: InputMaybe<Scalars['String']>;
|
||||||
majorVersion?: InputMaybe<Scalars['String']>;
|
majorVersion?: InputMaybe<Scalars['String']>;
|
||||||
|
oldEncryptColumnKey?: InputMaybe<Scalars['String']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConfigSystemConfigPostgresUpdateInput = {
|
export type ConfigSystemConfigPostgresUpdateInput = {
|
||||||
@@ -3201,7 +3207,9 @@ export type ConfigSystemConfigPostgresUpdateInput = {
|
|||||||
database?: InputMaybe<Scalars['String']>;
|
database?: InputMaybe<Scalars['String']>;
|
||||||
disk?: InputMaybe<ConfigSystemConfigPostgresDiskUpdateInput>;
|
disk?: InputMaybe<ConfigSystemConfigPostgresDiskUpdateInput>;
|
||||||
enabled?: InputMaybe<Scalars['Boolean']>;
|
enabled?: InputMaybe<Scalars['Boolean']>;
|
||||||
|
encryptColumnKey?: InputMaybe<Scalars['String']>;
|
||||||
majorVersion?: InputMaybe<Scalars['String']>;
|
majorVersion?: InputMaybe<Scalars['String']>;
|
||||||
|
oldEncryptColumnKey?: InputMaybe<Scalars['String']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConfigSystemConfigUpdateInput = {
|
export type ConfigSystemConfigUpdateInput = {
|
||||||
@@ -4426,6 +4434,7 @@ export type Apps = {
|
|||||||
billingDedicatedCompute?: Maybe<Billing_Dedicated_Compute>;
|
billingDedicatedCompute?: Maybe<Billing_Dedicated_Compute>;
|
||||||
/** An object relationship */
|
/** An object relationship */
|
||||||
billingSubscriptions?: Maybe<Billing_Subscriptions>;
|
billingSubscriptions?: Maybe<Billing_Subscriptions>;
|
||||||
|
/** main entrypoint to the configuration */
|
||||||
config?: Maybe<ConfigConfig>;
|
config?: Maybe<ConfigConfig>;
|
||||||
createdAt: Scalars['timestamptz'];
|
createdAt: Scalars['timestamptz'];
|
||||||
/** An object relationship */
|
/** An object relationship */
|
||||||
@@ -11094,6 +11103,7 @@ export type Deployments = {
|
|||||||
commitSHA: Scalars['String'];
|
commitSHA: Scalars['String'];
|
||||||
commitUserAvatarUrl?: Maybe<Scalars['String']>;
|
commitUserAvatarUrl?: Maybe<Scalars['String']>;
|
||||||
commitUserName?: Maybe<Scalars['String']>;
|
commitUserName?: Maybe<Scalars['String']>;
|
||||||
|
createdAt: Scalars['timestamptz'];
|
||||||
deploymentEndedAt?: Maybe<Scalars['timestamptz']>;
|
deploymentEndedAt?: Maybe<Scalars['timestamptz']>;
|
||||||
/** An array relationship */
|
/** An array relationship */
|
||||||
deploymentLogs: Array<DeploymentLogs>;
|
deploymentLogs: Array<DeploymentLogs>;
|
||||||
@@ -11191,6 +11201,7 @@ export type Deployments_Bool_Exp = {
|
|||||||
commitSHA?: InputMaybe<String_Comparison_Exp>;
|
commitSHA?: InputMaybe<String_Comparison_Exp>;
|
||||||
commitUserAvatarUrl?: InputMaybe<String_Comparison_Exp>;
|
commitUserAvatarUrl?: InputMaybe<String_Comparison_Exp>;
|
||||||
commitUserName?: InputMaybe<String_Comparison_Exp>;
|
commitUserName?: InputMaybe<String_Comparison_Exp>;
|
||||||
|
createdAt?: InputMaybe<Timestamptz_Comparison_Exp>;
|
||||||
deploymentEndedAt?: InputMaybe<Timestamptz_Comparison_Exp>;
|
deploymentEndedAt?: InputMaybe<Timestamptz_Comparison_Exp>;
|
||||||
deploymentLogs?: InputMaybe<DeploymentLogs_Bool_Exp>;
|
deploymentLogs?: InputMaybe<DeploymentLogs_Bool_Exp>;
|
||||||
deploymentLogs_aggregate?: InputMaybe<DeploymentLogs_Aggregate_Bool_Exp>;
|
deploymentLogs_aggregate?: InputMaybe<DeploymentLogs_Aggregate_Bool_Exp>;
|
||||||
@@ -11222,6 +11233,7 @@ export type Deployments_Insert_Input = {
|
|||||||
commitSHA?: InputMaybe<Scalars['String']>;
|
commitSHA?: InputMaybe<Scalars['String']>;
|
||||||
commitUserAvatarUrl?: InputMaybe<Scalars['String']>;
|
commitUserAvatarUrl?: InputMaybe<Scalars['String']>;
|
||||||
commitUserName?: InputMaybe<Scalars['String']>;
|
commitUserName?: InputMaybe<Scalars['String']>;
|
||||||
|
createdAt?: InputMaybe<Scalars['timestamptz']>;
|
||||||
deploymentEndedAt?: InputMaybe<Scalars['timestamptz']>;
|
deploymentEndedAt?: InputMaybe<Scalars['timestamptz']>;
|
||||||
deploymentLogs?: InputMaybe<DeploymentLogs_Arr_Rel_Insert_Input>;
|
deploymentLogs?: InputMaybe<DeploymentLogs_Arr_Rel_Insert_Input>;
|
||||||
deploymentStartedAt?: InputMaybe<Scalars['timestamptz']>;
|
deploymentStartedAt?: InputMaybe<Scalars['timestamptz']>;
|
||||||
@@ -11246,6 +11258,7 @@ export type Deployments_Max_Fields = {
|
|||||||
commitSHA?: Maybe<Scalars['String']>;
|
commitSHA?: Maybe<Scalars['String']>;
|
||||||
commitUserAvatarUrl?: Maybe<Scalars['String']>;
|
commitUserAvatarUrl?: Maybe<Scalars['String']>;
|
||||||
commitUserName?: Maybe<Scalars['String']>;
|
commitUserName?: Maybe<Scalars['String']>;
|
||||||
|
createdAt?: Maybe<Scalars['timestamptz']>;
|
||||||
deploymentEndedAt?: Maybe<Scalars['timestamptz']>;
|
deploymentEndedAt?: Maybe<Scalars['timestamptz']>;
|
||||||
deploymentStartedAt?: Maybe<Scalars['timestamptz']>;
|
deploymentStartedAt?: Maybe<Scalars['timestamptz']>;
|
||||||
deploymentStatus?: Maybe<Scalars['String']>;
|
deploymentStatus?: Maybe<Scalars['String']>;
|
||||||
@@ -11268,6 +11281,7 @@ export type Deployments_Max_Order_By = {
|
|||||||
commitSHA?: InputMaybe<Order_By>;
|
commitSHA?: InputMaybe<Order_By>;
|
||||||
commitUserAvatarUrl?: InputMaybe<Order_By>;
|
commitUserAvatarUrl?: InputMaybe<Order_By>;
|
||||||
commitUserName?: InputMaybe<Order_By>;
|
commitUserName?: InputMaybe<Order_By>;
|
||||||
|
createdAt?: InputMaybe<Order_By>;
|
||||||
deploymentEndedAt?: InputMaybe<Order_By>;
|
deploymentEndedAt?: InputMaybe<Order_By>;
|
||||||
deploymentStartedAt?: InputMaybe<Order_By>;
|
deploymentStartedAt?: InputMaybe<Order_By>;
|
||||||
deploymentStatus?: InputMaybe<Order_By>;
|
deploymentStatus?: InputMaybe<Order_By>;
|
||||||
@@ -11291,6 +11305,7 @@ export type Deployments_Min_Fields = {
|
|||||||
commitSHA?: Maybe<Scalars['String']>;
|
commitSHA?: Maybe<Scalars['String']>;
|
||||||
commitUserAvatarUrl?: Maybe<Scalars['String']>;
|
commitUserAvatarUrl?: Maybe<Scalars['String']>;
|
||||||
commitUserName?: Maybe<Scalars['String']>;
|
commitUserName?: Maybe<Scalars['String']>;
|
||||||
|
createdAt?: Maybe<Scalars['timestamptz']>;
|
||||||
deploymentEndedAt?: Maybe<Scalars['timestamptz']>;
|
deploymentEndedAt?: Maybe<Scalars['timestamptz']>;
|
||||||
deploymentStartedAt?: Maybe<Scalars['timestamptz']>;
|
deploymentStartedAt?: Maybe<Scalars['timestamptz']>;
|
||||||
deploymentStatus?: Maybe<Scalars['String']>;
|
deploymentStatus?: Maybe<Scalars['String']>;
|
||||||
@@ -11313,6 +11328,7 @@ export type Deployments_Min_Order_By = {
|
|||||||
commitSHA?: InputMaybe<Order_By>;
|
commitSHA?: InputMaybe<Order_By>;
|
||||||
commitUserAvatarUrl?: InputMaybe<Order_By>;
|
commitUserAvatarUrl?: InputMaybe<Order_By>;
|
||||||
commitUserName?: InputMaybe<Order_By>;
|
commitUserName?: InputMaybe<Order_By>;
|
||||||
|
createdAt?: InputMaybe<Order_By>;
|
||||||
deploymentEndedAt?: InputMaybe<Order_By>;
|
deploymentEndedAt?: InputMaybe<Order_By>;
|
||||||
deploymentStartedAt?: InputMaybe<Order_By>;
|
deploymentStartedAt?: InputMaybe<Order_By>;
|
||||||
deploymentStatus?: InputMaybe<Order_By>;
|
deploymentStatus?: InputMaybe<Order_By>;
|
||||||
@@ -11359,6 +11375,7 @@ export type Deployments_Order_By = {
|
|||||||
commitSHA?: InputMaybe<Order_By>;
|
commitSHA?: InputMaybe<Order_By>;
|
||||||
commitUserAvatarUrl?: InputMaybe<Order_By>;
|
commitUserAvatarUrl?: InputMaybe<Order_By>;
|
||||||
commitUserName?: InputMaybe<Order_By>;
|
commitUserName?: InputMaybe<Order_By>;
|
||||||
|
createdAt?: InputMaybe<Order_By>;
|
||||||
deploymentEndedAt?: InputMaybe<Order_By>;
|
deploymentEndedAt?: InputMaybe<Order_By>;
|
||||||
deploymentLogs_aggregate?: InputMaybe<DeploymentLogs_Aggregate_Order_By>;
|
deploymentLogs_aggregate?: InputMaybe<DeploymentLogs_Aggregate_Order_By>;
|
||||||
deploymentStartedAt?: InputMaybe<Order_By>;
|
deploymentStartedAt?: InputMaybe<Order_By>;
|
||||||
@@ -11393,6 +11410,8 @@ export enum Deployments_Select_Column {
|
|||||||
/** column name */
|
/** column name */
|
||||||
CommitUserName = 'commitUserName',
|
CommitUserName = 'commitUserName',
|
||||||
/** column name */
|
/** column name */
|
||||||
|
CreatedAt = 'createdAt',
|
||||||
|
/** column name */
|
||||||
DeploymentEndedAt = 'deploymentEndedAt',
|
DeploymentEndedAt = 'deploymentEndedAt',
|
||||||
/** column name */
|
/** column name */
|
||||||
DeploymentStartedAt = 'deploymentStartedAt',
|
DeploymentStartedAt = 'deploymentStartedAt',
|
||||||
@@ -11427,6 +11446,7 @@ export type Deployments_Set_Input = {
|
|||||||
commitSHA?: InputMaybe<Scalars['String']>;
|
commitSHA?: InputMaybe<Scalars['String']>;
|
||||||
commitUserAvatarUrl?: InputMaybe<Scalars['String']>;
|
commitUserAvatarUrl?: InputMaybe<Scalars['String']>;
|
||||||
commitUserName?: InputMaybe<Scalars['String']>;
|
commitUserName?: InputMaybe<Scalars['String']>;
|
||||||
|
createdAt?: InputMaybe<Scalars['timestamptz']>;
|
||||||
deploymentEndedAt?: InputMaybe<Scalars['timestamptz']>;
|
deploymentEndedAt?: InputMaybe<Scalars['timestamptz']>;
|
||||||
deploymentStartedAt?: InputMaybe<Scalars['timestamptz']>;
|
deploymentStartedAt?: InputMaybe<Scalars['timestamptz']>;
|
||||||
deploymentStatus?: InputMaybe<Scalars['String']>;
|
deploymentStatus?: InputMaybe<Scalars['String']>;
|
||||||
@@ -11457,6 +11477,7 @@ export type Deployments_Stream_Cursor_Value_Input = {
|
|||||||
commitSHA?: InputMaybe<Scalars['String']>;
|
commitSHA?: InputMaybe<Scalars['String']>;
|
||||||
commitUserAvatarUrl?: InputMaybe<Scalars['String']>;
|
commitUserAvatarUrl?: InputMaybe<Scalars['String']>;
|
||||||
commitUserName?: InputMaybe<Scalars['String']>;
|
commitUserName?: InputMaybe<Scalars['String']>;
|
||||||
|
createdAt?: InputMaybe<Scalars['timestamptz']>;
|
||||||
deploymentEndedAt?: InputMaybe<Scalars['timestamptz']>;
|
deploymentEndedAt?: InputMaybe<Scalars['timestamptz']>;
|
||||||
deploymentStartedAt?: InputMaybe<Scalars['timestamptz']>;
|
deploymentStartedAt?: InputMaybe<Scalars['timestamptz']>;
|
||||||
deploymentStatus?: InputMaybe<Scalars['String']>;
|
deploymentStatus?: InputMaybe<Scalars['String']>;
|
||||||
@@ -11485,6 +11506,8 @@ export enum Deployments_Update_Column {
|
|||||||
/** column name */
|
/** column name */
|
||||||
CommitUserName = 'commitUserName',
|
CommitUserName = 'commitUserName',
|
||||||
/** column name */
|
/** column name */
|
||||||
|
CreatedAt = 'createdAt',
|
||||||
|
/** column name */
|
||||||
DeploymentEndedAt = 'deploymentEndedAt',
|
DeploymentEndedAt = 'deploymentEndedAt',
|
||||||
/** column name */
|
/** column name */
|
||||||
DeploymentStartedAt = 'deploymentStartedAt',
|
DeploymentStartedAt = 'deploymentStartedAt',
|
||||||
@@ -13067,6 +13090,7 @@ export type Mutation_Root = {
|
|||||||
billingUpgradeFreeOrganization: Scalars['String'];
|
billingUpgradeFreeOrganization: Scalars['String'];
|
||||||
billingUploadReports: Scalars['Boolean'];
|
billingUploadReports: Scalars['Boolean'];
|
||||||
changeDatabaseVersion: Scalars['Boolean'];
|
changeDatabaseVersion: Scalars['Boolean'];
|
||||||
|
connectGithubRepo: Scalars['Boolean'];
|
||||||
/** delete single row from the table: "announcements_read" */
|
/** delete single row from the table: "announcements_read" */
|
||||||
deleteAnnouncementRead?: Maybe<Announcements_Read>;
|
deleteAnnouncementRead?: Maybe<Announcements_Read>;
|
||||||
/** delete data from the table: "announcements_read" */
|
/** delete data from the table: "announcements_read" */
|
||||||
@@ -13968,6 +13992,15 @@ export type Mutation_RootChangeDatabaseVersionArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** mutation root */
|
||||||
|
export type Mutation_RootConnectGithubRepoArgs = {
|
||||||
|
appID: Scalars['uuid'];
|
||||||
|
baseFolder: Scalars['String'];
|
||||||
|
githubNodeID: Scalars['String'];
|
||||||
|
productionBranch: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/** mutation root */
|
/** mutation root */
|
||||||
export type Mutation_RootDeleteAnnouncementReadArgs = {
|
export type Mutation_RootDeleteAnnouncementReadArgs = {
|
||||||
id: Scalars['uuid'];
|
id: Scalars['uuid'];
|
||||||
@@ -27517,6 +27550,16 @@ export type ResetDatabasePasswordMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type ResetDatabasePasswordMutation = { __typename?: 'mutation_root', resetPostgresPassword: boolean };
|
export type ResetDatabasePasswordMutation = { __typename?: 'mutation_root', resetPostgresPassword: boolean };
|
||||||
|
|
||||||
|
export type ConnectGithubRepoMutationVariables = Exact<{
|
||||||
|
appID: Scalars['uuid'];
|
||||||
|
githubNodeID: Scalars['String'];
|
||||||
|
productionBranch: Scalars['String'];
|
||||||
|
baseFolder: Scalars['String'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type ConnectGithubRepoMutation = { __typename?: 'mutation_root', connectGithubRepo: boolean };
|
||||||
|
|
||||||
export type GetHasuraSettingsQueryVariables = Exact<{
|
export type GetHasuraSettingsQueryVariables = Exact<{
|
||||||
appId: Scalars['uuid'];
|
appId: Scalars['uuid'];
|
||||||
}>;
|
}>;
|
||||||
@@ -29446,6 +29489,45 @@ export function useResetDatabasePasswordMutation(baseOptions?: Apollo.MutationHo
|
|||||||
export type ResetDatabasePasswordMutationHookResult = ReturnType<typeof useResetDatabasePasswordMutation>;
|
export type ResetDatabasePasswordMutationHookResult = ReturnType<typeof useResetDatabasePasswordMutation>;
|
||||||
export type ResetDatabasePasswordMutationResult = Apollo.MutationResult<ResetDatabasePasswordMutation>;
|
export type ResetDatabasePasswordMutationResult = Apollo.MutationResult<ResetDatabasePasswordMutation>;
|
||||||
export type ResetDatabasePasswordMutationOptions = Apollo.BaseMutationOptions<ResetDatabasePasswordMutation, ResetDatabasePasswordMutationVariables>;
|
export type ResetDatabasePasswordMutationOptions = Apollo.BaseMutationOptions<ResetDatabasePasswordMutation, ResetDatabasePasswordMutationVariables>;
|
||||||
|
export const ConnectGithubRepoDocument = gql`
|
||||||
|
mutation ConnectGithubRepo($appID: uuid!, $githubNodeID: String!, $productionBranch: String!, $baseFolder: String!) {
|
||||||
|
connectGithubRepo(
|
||||||
|
appID: $appID
|
||||||
|
githubNodeID: $githubNodeID
|
||||||
|
productionBranch: $productionBranch
|
||||||
|
baseFolder: $baseFolder
|
||||||
|
)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type ConnectGithubRepoMutationFn = Apollo.MutationFunction<ConnectGithubRepoMutation, ConnectGithubRepoMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useConnectGithubRepoMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useConnectGithubRepoMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useConnectGithubRepoMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [connectGithubRepoMutation, { data, loading, error }] = useConnectGithubRepoMutation({
|
||||||
|
* variables: {
|
||||||
|
* appID: // value for 'appID'
|
||||||
|
* githubNodeID: // value for 'githubNodeID'
|
||||||
|
* productionBranch: // value for 'productionBranch'
|
||||||
|
* baseFolder: // value for 'baseFolder'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useConnectGithubRepoMutation(baseOptions?: Apollo.MutationHookOptions<ConnectGithubRepoMutation, ConnectGithubRepoMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<ConnectGithubRepoMutation, ConnectGithubRepoMutationVariables>(ConnectGithubRepoDocument, options);
|
||||||
|
}
|
||||||
|
export type ConnectGithubRepoMutationHookResult = ReturnType<typeof useConnectGithubRepoMutation>;
|
||||||
|
export type ConnectGithubRepoMutationResult = Apollo.MutationResult<ConnectGithubRepoMutation>;
|
||||||
|
export type ConnectGithubRepoMutationOptions = Apollo.BaseMutationOptions<ConnectGithubRepoMutation, ConnectGithubRepoMutationVariables>;
|
||||||
export const GetHasuraSettingsDocument = gql`
|
export const GetHasuraSettingsDocument = gql`
|
||||||
query GetHasuraSettings($appId: uuid!) {
|
query GetHasuraSettings($appId: uuid!) {
|
||||||
config(appID: $appId, resolve: false) {
|
config(appID: $appId, resolve: false) {
|
||||||
|
|||||||
@@ -206,8 +206,7 @@ paths:
|
|||||||
Last-Modified:
|
Last-Modified:
|
||||||
description: "Date and time the file was last modified"
|
description: "Date and time the file was last modified"
|
||||||
schema:
|
schema:
|
||||||
type: string
|
$ref: '#/components/schemas/RFC2822Date'
|
||||||
format: date-time
|
|
||||||
Surrogate-Key:
|
Surrogate-Key:
|
||||||
description: "Cache key for surrogate caching"
|
description: "Cache key for surrogate caching"
|
||||||
schema:
|
schema:
|
||||||
@@ -248,8 +247,7 @@ paths:
|
|||||||
Last-Modified:
|
Last-Modified:
|
||||||
description: "Date and time the file was last modified"
|
description: "Date and time the file was last modified"
|
||||||
schema:
|
schema:
|
||||||
type: string
|
$ref: '#/components/schemas/RFC2822Date'
|
||||||
format: date-time
|
|
||||||
Surrogate-Key:
|
Surrogate-Key:
|
||||||
description: "Cache key for surrogate caching"
|
description: "Cache key for surrogate caching"
|
||||||
schema:
|
schema:
|
||||||
@@ -391,8 +389,7 @@ paths:
|
|||||||
Last-Modified:
|
Last-Modified:
|
||||||
description: "Date and time the file was last modified"
|
description: "Date and time the file was last modified"
|
||||||
schema:
|
schema:
|
||||||
type: string
|
$ref: '#/components/schemas/RFC2822Date'
|
||||||
format: date-time
|
|
||||||
Accept-Ranges:
|
Accept-Ranges:
|
||||||
description: "Always set to bytes. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges"
|
description: "Always set to bytes. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges"
|
||||||
schema:
|
schema:
|
||||||
@@ -675,8 +672,7 @@ paths:
|
|||||||
Last-Modified:
|
Last-Modified:
|
||||||
description: "Date and time the file was last modified"
|
description: "Date and time the file was last modified"
|
||||||
schema:
|
schema:
|
||||||
type: string
|
$ref: '#/components/schemas/RFC2822Date'
|
||||||
format: date-time
|
|
||||||
Surrogate-Key:
|
Surrogate-Key:
|
||||||
description: "Cache key for surrogate caching"
|
description: "Cache key for surrogate caching"
|
||||||
schema:
|
schema:
|
||||||
@@ -717,8 +713,7 @@ paths:
|
|||||||
Last-Modified:
|
Last-Modified:
|
||||||
description: "Date and time the file was last modified"
|
description: "Date and time the file was last modified"
|
||||||
schema:
|
schema:
|
||||||
type: string
|
$ref: '#/components/schemas/RFC2822Date'
|
||||||
format: date-time
|
|
||||||
Surrogate-Key:
|
Surrogate-Key:
|
||||||
description: "Cache key for surrogate caching"
|
description: "Cache key for surrogate caching"
|
||||||
schema:
|
schema:
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -154,7 +154,7 @@ require (
|
|||||||
github.com/prometheus/common v0.63.0 // indirect
|
github.com/prometheus/common v0.63.0 // indirect
|
||||||
github.com/prometheus/procfs v0.16.1 // indirect
|
github.com/prometheus/procfs v0.16.1 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
github.com/quic-go/quic-go v0.54.1 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/rs/cors v1.11.0 // indirect
|
github.com/rs/cors v1.11.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -381,8 +381,8 @@ github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq
|
|||||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
|
github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
github.com/quic-go/quic-go v0.54.1 h1:4ZAWm0AhCb6+hE+l5Q1NAL0iRn/ZrMwqHRGQiFwj2eg=
|
||||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
github.com/quic-go/quic-go v0.54.1/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
## [storage@0.9.1] - 2025-11-06
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- *(storage)* Format date-time headers with RFC2822 (#3672)
|
||||||
|
|
||||||
## [storage@0.9.0] - 2025-11-04
|
## [storage@0.9.0] - 2025-11-04
|
||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
const format = time.RFC1123
|
const format = time.RFC1123
|
||||||
|
|
||||||
type Time time.Time
|
type Time time.Time //nolint:recvcheck
|
||||||
|
|
||||||
func (dt *Time) UnmarshalText(text []byte) error {
|
func (dt *Time) UnmarshalText(text []byte) error {
|
||||||
if len(text) == 0 || string(text) == `""` {
|
if len(text) == 0 || string(text) == `""` {
|
||||||
@@ -24,6 +24,14 @@ func (dt *Time) UnmarshalText(text []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dt Time) String() string {
|
||||||
|
if time.Time(dt).IsZero() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Time(dt).Format(format)
|
||||||
|
}
|
||||||
|
|
||||||
func Date(year int, month time.Month, day, hour, minute, sec, nsec int, loc *time.Location) Time {
|
func Date(year int, month time.Month, day, hour, minute, sec, nsec int, loc *time.Location) Time {
|
||||||
return Time(time.Date(year, month, day, hour, minute, sec, nsec, loc))
|
return Time(time.Date(year, month, day, hour, minute, sec, nsec, loc))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/getkin/kin-openapi/openapi3"
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -998,7 +997,7 @@ type GetFile200ResponseHeaders struct {
|
|||||||
ContentDisposition string
|
ContentDisposition string
|
||||||
ContentType string
|
ContentType string
|
||||||
Etag string
|
Etag string
|
||||||
LastModified time.Time
|
LastModified RFC2822Date
|
||||||
SurrogateControl string
|
SurrogateControl string
|
||||||
SurrogateKey string
|
SurrogateKey string
|
||||||
}
|
}
|
||||||
@@ -1037,7 +1036,7 @@ type GetFile206ResponseHeaders struct {
|
|||||||
ContentRange string
|
ContentRange string
|
||||||
ContentType string
|
ContentType string
|
||||||
Etag string
|
Etag string
|
||||||
LastModified time.Time
|
LastModified RFC2822Date
|
||||||
SurrogateControl string
|
SurrogateControl string
|
||||||
SurrogateKey string
|
SurrogateKey string
|
||||||
}
|
}
|
||||||
@@ -1138,7 +1137,7 @@ type GetFileMetadataHeaders200ResponseHeaders struct {
|
|||||||
ContentLength int
|
ContentLength int
|
||||||
ContentType string
|
ContentType string
|
||||||
Etag string
|
Etag string
|
||||||
LastModified time.Time
|
LastModified RFC2822Date
|
||||||
SurrogateControl string
|
SurrogateControl string
|
||||||
SurrogateKey string
|
SurrogateKey string
|
||||||
}
|
}
|
||||||
@@ -1287,7 +1286,7 @@ type GetFileWithPresignedURL200ResponseHeaders struct {
|
|||||||
ContentDisposition string
|
ContentDisposition string
|
||||||
ContentType string
|
ContentType string
|
||||||
Etag string
|
Etag string
|
||||||
LastModified time.Time
|
LastModified RFC2822Date
|
||||||
SurrogateControl string
|
SurrogateControl string
|
||||||
SurrogateKey string
|
SurrogateKey string
|
||||||
}
|
}
|
||||||
@@ -1326,7 +1325,7 @@ type GetFileWithPresignedURL206ResponseHeaders struct {
|
|||||||
ContentRange string
|
ContentRange string
|
||||||
ContentType string
|
ContentType string
|
||||||
Etag string
|
Etag string
|
||||||
LastModified time.Time
|
LastModified RFC2822Date
|
||||||
SurrogateControl string
|
SurrogateControl string
|
||||||
SurrogateKey string
|
SurrogateKey string
|
||||||
}
|
}
|
||||||
@@ -2117,33 +2116,33 @@ var swaggerSpec = []string{
|
|||||||
"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",
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSwagger returns the content of the embedded swagger specification file
|
// GetSwagger returns the content of the embedded swagger specification file
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ func (ctrl *Controller) getFileResponse( //nolint: ireturn,dupl
|
|||||||
),
|
),
|
||||||
ContentType: file.mimeType,
|
ContentType: file.mimeType,
|
||||||
Etag: file.fileMetadata.Etag,
|
Etag: file.fileMetadata.Etag,
|
||||||
LastModified: file.fileMetadata.UpdatedAt,
|
LastModified: api.RFC2822Date(file.fileMetadata.UpdatedAt),
|
||||||
SurrogateControl: file.cacheControl,
|
SurrogateControl: file.cacheControl,
|
||||||
SurrogateKey: file.fileMetadata.Id,
|
SurrogateKey: file.fileMetadata.Id,
|
||||||
},
|
},
|
||||||
@@ -290,7 +290,7 @@ func (ctrl *Controller) getFileResponse( //nolint: ireturn,dupl
|
|||||||
ContentRange: file.extraHeaders.Get("Content-Range"),
|
ContentRange: file.extraHeaders.Get("Content-Range"),
|
||||||
ContentType: file.mimeType,
|
ContentType: file.mimeType,
|
||||||
Etag: file.fileMetadata.Etag,
|
Etag: file.fileMetadata.Etag,
|
||||||
LastModified: file.fileMetadata.UpdatedAt,
|
LastModified: api.RFC2822Date(file.fileMetadata.UpdatedAt),
|
||||||
SurrogateControl: file.cacheControl,
|
SurrogateControl: file.cacheControl,
|
||||||
SurrogateKey: file.fileMetadata.Id,
|
SurrogateKey: file.fileMetadata.Id,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ func (ctrl *Controller) getFileMetadataHeadersResponseObject( //nolint:ireturn
|
|||||||
CacheControl: bucketMetadata.CacheControl,
|
CacheControl: bucketMetadata.CacheControl,
|
||||||
ContentType: fileMetadata.MimeType,
|
ContentType: fileMetadata.MimeType,
|
||||||
Etag: fileMetadata.Etag,
|
Etag: fileMetadata.Etag,
|
||||||
LastModified: fileMetadata.UpdatedAt,
|
LastModified: api.RFC2822Date(fileMetadata.UpdatedAt),
|
||||||
SurrogateControl: bucketMetadata.CacheControl,
|
SurrogateControl: bucketMetadata.CacheControl,
|
||||||
SurrogateKey: fileMetadata.Id,
|
SurrogateKey: fileMetadata.Id,
|
||||||
ContentDisposition: fmt.Sprintf(
|
ContentDisposition: fmt.Sprintf(
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func TestGetFileInfo(t *testing.T) {
|
|||||||
ContentLength: 64,
|
ContentLength: 64,
|
||||||
ContentType: "text/plain; charset=utf-8",
|
ContentType: "text/plain; charset=utf-8",
|
||||||
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
||||||
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
||||||
SurrogateControl: "max-age=3600",
|
SurrogateControl: "max-age=3600",
|
||||||
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
||||||
},
|
},
|
||||||
@@ -57,7 +57,7 @@ func TestGetFileInfo(t *testing.T) {
|
|||||||
ContentLength: 64,
|
ContentLength: 64,
|
||||||
ContentType: "text/plain; charset=utf-8",
|
ContentType: "text/plain; charset=utf-8",
|
||||||
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
||||||
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
||||||
SurrogateControl: "max-age=3600",
|
SurrogateControl: "max-age=3600",
|
||||||
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
||||||
},
|
},
|
||||||
@@ -111,7 +111,7 @@ func TestGetFileInfo(t *testing.T) {
|
|||||||
ContentLength: 64,
|
ContentLength: 64,
|
||||||
ContentType: "text/plain; charset=utf-8",
|
ContentType: "text/plain; charset=utf-8",
|
||||||
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
||||||
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
||||||
SurrogateControl: "max-age=3600",
|
SurrogateControl: "max-age=3600",
|
||||||
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
||||||
},
|
},
|
||||||
@@ -133,7 +133,7 @@ func TestGetFileInfo(t *testing.T) {
|
|||||||
ContentLength: 64,
|
ContentLength: 64,
|
||||||
ContentType: "text/plain; charset=utf-8",
|
ContentType: "text/plain; charset=utf-8",
|
||||||
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
||||||
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
||||||
SurrogateControl: "max-age=3600",
|
SurrogateControl: "max-age=3600",
|
||||||
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
||||||
},
|
},
|
||||||
@@ -171,7 +171,7 @@ func TestGetFileInfo(t *testing.T) {
|
|||||||
ContentLength: 64,
|
ContentLength: 64,
|
||||||
ContentType: "text/plain; charset=utf-8",
|
ContentType: "text/plain; charset=utf-8",
|
||||||
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
||||||
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
||||||
SurrogateControl: "max-age=3600",
|
SurrogateControl: "max-age=3600",
|
||||||
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func TestGetFile(t *testing.T) { //nolint:maintidx
|
|||||||
ContentDisposition: `inline; filename="my-file.txt"`,
|
ContentDisposition: `inline; filename="my-file.txt"`,
|
||||||
ContentType: "text/plain; charset=utf-8",
|
ContentType: "text/plain; charset=utf-8",
|
||||||
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
||||||
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
||||||
SurrogateControl: "max-age=3600",
|
SurrogateControl: "max-age=3600",
|
||||||
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
||||||
},
|
},
|
||||||
@@ -62,7 +62,7 @@ func TestGetFile(t *testing.T) { //nolint:maintidx
|
|||||||
ContentDisposition: `inline; filename="my-file.txt"`,
|
ContentDisposition: `inline; filename="my-file.txt"`,
|
||||||
ContentType: "text/plain; charset=utf-8",
|
ContentType: "text/plain; charset=utf-8",
|
||||||
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
||||||
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
||||||
SurrogateControl: "max-age=3600",
|
SurrogateControl: "max-age=3600",
|
||||||
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
||||||
},
|
},
|
||||||
@@ -116,7 +116,7 @@ func TestGetFile(t *testing.T) { //nolint:maintidx
|
|||||||
ContentDisposition: `inline; filename="my-file.txt"`,
|
ContentDisposition: `inline; filename="my-file.txt"`,
|
||||||
ContentType: "text/plain; charset=utf-8",
|
ContentType: "text/plain; charset=utf-8",
|
||||||
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
||||||
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
||||||
SurrogateControl: "max-age=3600",
|
SurrogateControl: "max-age=3600",
|
||||||
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
||||||
},
|
},
|
||||||
@@ -138,7 +138,7 @@ func TestGetFile(t *testing.T) { //nolint:maintidx
|
|||||||
ContentDisposition: `inline; filename="my-file.txt"`,
|
ContentDisposition: `inline; filename="my-file.txt"`,
|
||||||
ContentType: "text/plain; charset=utf-8",
|
ContentType: "text/plain; charset=utf-8",
|
||||||
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
||||||
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
||||||
SurrogateControl: "max-age=3600",
|
SurrogateControl: "max-age=3600",
|
||||||
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
||||||
},
|
},
|
||||||
@@ -176,7 +176,7 @@ func TestGetFile(t *testing.T) { //nolint:maintidx
|
|||||||
ContentDisposition: `inline; filename="my-file.txt"`,
|
ContentDisposition: `inline; filename="my-file.txt"`,
|
||||||
ContentType: "text/plain; charset=utf-8",
|
ContentType: "text/plain; charset=utf-8",
|
||||||
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
|
||||||
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
|
||||||
SurrogateControl: "max-age=3600",
|
SurrogateControl: "max-age=3600",
|
||||||
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func (ctrl *Controller) getFileWithPresignedURLResponseObject( //nolint: ireturn
|
|||||||
),
|
),
|
||||||
ContentType: file.mimeType,
|
ContentType: file.mimeType,
|
||||||
Etag: file.fileMetadata.Etag,
|
Etag: file.fileMetadata.Etag,
|
||||||
LastModified: file.fileMetadata.UpdatedAt,
|
LastModified: api.RFC2822Date(file.fileMetadata.UpdatedAt),
|
||||||
SurrogateControl: file.cacheControl,
|
SurrogateControl: file.cacheControl,
|
||||||
SurrogateKey: file.fileMetadata.Id,
|
SurrogateKey: file.fileMetadata.Id,
|
||||||
},
|
},
|
||||||
@@ -117,7 +117,7 @@ func (ctrl *Controller) getFileWithPresignedURLResponseObject( //nolint: ireturn
|
|||||||
ContentRange: file.extraHeaders.Get("Content-Range"),
|
ContentRange: file.extraHeaders.Get("Content-Range"),
|
||||||
ContentType: file.mimeType,
|
ContentType: file.mimeType,
|
||||||
Etag: file.fileMetadata.Etag,
|
Etag: file.fileMetadata.Etag,
|
||||||
LastModified: file.fileMetadata.UpdatedAt,
|
LastModified: api.RFC2822Date(file.fileMetadata.UpdatedAt),
|
||||||
SurrogateControl: file.cacheControl,
|
SurrogateControl: file.cacheControl,
|
||||||
SurrogateKey: file.fileMetadata.Id,
|
SurrogateKey: file.fileMetadata.Id,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ func FileMetadataMatcher(v api.FileMetadata) gomock.Matcher {
|
|||||||
func assert(t *testing.T, got, wanted interface{}, opts ...cmp.Option) {
|
func assert(t *testing.T, got, wanted interface{}, opts ...cmp.Option) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
opts = append(opts, cmpopts.IgnoreUnexported(
|
||||||
|
api.Time{},
|
||||||
|
))
|
||||||
|
|
||||||
if !cmp.Equal(got, wanted, opts...) {
|
if !cmp.Equal(got, wanted, opts...) {
|
||||||
t.Error(cmp.Diff(got, wanted, opts...))
|
t.Error(cmp.Diff(got, wanted, opts...))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,8 +206,7 @@ paths:
|
|||||||
Last-Modified:
|
Last-Modified:
|
||||||
description: "Date and time the file was last modified"
|
description: "Date and time the file was last modified"
|
||||||
schema:
|
schema:
|
||||||
type: string
|
$ref: '#/components/schemas/RFC2822Date'
|
||||||
format: date-time
|
|
||||||
Surrogate-Key:
|
Surrogate-Key:
|
||||||
description: "Cache key for surrogate caching"
|
description: "Cache key for surrogate caching"
|
||||||
schema:
|
schema:
|
||||||
@@ -248,8 +247,7 @@ paths:
|
|||||||
Last-Modified:
|
Last-Modified:
|
||||||
description: "Date and time the file was last modified"
|
description: "Date and time the file was last modified"
|
||||||
schema:
|
schema:
|
||||||
type: string
|
$ref: '#/components/schemas/RFC2822Date'
|
||||||
format: date-time
|
|
||||||
Surrogate-Key:
|
Surrogate-Key:
|
||||||
description: "Cache key for surrogate caching"
|
description: "Cache key for surrogate caching"
|
||||||
schema:
|
schema:
|
||||||
@@ -391,8 +389,7 @@ paths:
|
|||||||
Last-Modified:
|
Last-Modified:
|
||||||
description: "Date and time the file was last modified"
|
description: "Date and time the file was last modified"
|
||||||
schema:
|
schema:
|
||||||
type: string
|
$ref: '#/components/schemas/RFC2822Date'
|
||||||
format: date-time
|
|
||||||
Accept-Ranges:
|
Accept-Ranges:
|
||||||
description: "Always set to bytes. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges"
|
description: "Always set to bytes. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges"
|
||||||
schema:
|
schema:
|
||||||
@@ -675,8 +672,7 @@ paths:
|
|||||||
Last-Modified:
|
Last-Modified:
|
||||||
description: "Date and time the file was last modified"
|
description: "Date and time the file was last modified"
|
||||||
schema:
|
schema:
|
||||||
type: string
|
$ref: '#/components/schemas/RFC2822Date'
|
||||||
format: date-time
|
|
||||||
Surrogate-Key:
|
Surrogate-Key:
|
||||||
description: "Cache key for surrogate caching"
|
description: "Cache key for surrogate caching"
|
||||||
schema:
|
schema:
|
||||||
@@ -717,8 +713,7 @@ paths:
|
|||||||
Last-Modified:
|
Last-Modified:
|
||||||
description: "Date and time the file was last modified"
|
description: "Date and time the file was last modified"
|
||||||
schema:
|
schema:
|
||||||
type: string
|
$ref: '#/components/schemas/RFC2822Date'
|
||||||
format: date-time
|
|
||||||
Surrogate-Key:
|
Surrogate-Key:
|
||||||
description: "Cache key for surrogate caching"
|
description: "Cache key for surrogate caching"
|
||||||
schema:
|
schema:
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
URL=http://localhost:8000/v1/files
|
URL=http://localhost:8000/v1/files
|
||||||
AUTH="Authorization: Bearer $(make dev-jwt)"
|
|
||||||
|
# token can be generated using build/dev/jwt-gen
|
||||||
|
AUTH="Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwNzc3NzY0NjYsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtYWxsb3dlZC1yb2xlcyI6WyJhZG1pbiJdLCJ4LWhhc3VyYS1kZWZhdWx0LXJvbGUiOiJhZG1pbiIsIngtaGFzdXJhLXVzZXItaWQiOiJhYjViYTU4ZS05MzJhLTQwZGMtODdlOC03MzM5OTg3OTRlYzIiLCJ4LWhhc3VyYS11c2VyLWlzQW5vbnltb3VzIjoiZmFsc2UifSwiaWF0IjoxNzYyNDE2NDY2LCJpc3MiOiJoYXN1cmEtYXV0aCIsInN1YiI6ImFiNWJhNThlLTkzMmEtNDBkYy04N2U4LTczMzk5ODc5NGVjMiJ9.msexXYDRzox0giNGRHqPrefYH_uWXMtdCbEZ_Vg-IV8"
|
||||||
BUCKET=default
|
BUCKET=default
|
||||||
|
|
||||||
FILE_ID=55af1e60-0f28-454e-885e-ea6aab2bb288
|
FILE_ID=55af1e60-0f28-454e-885e-ea6aab2bb288
|
||||||
@@ -11,14 +13,12 @@ ETAG=\"588be441fe7a59460850b0aa3e1c5a65\"
|
|||||||
# lead to a JWTIssuedAtFuture error
|
# lead to a JWTIssuedAtFuture error
|
||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
output=`curl $URL/ \
|
output=`curl $URL \
|
||||||
-v \
|
-v \
|
||||||
-H "$AUTH" \
|
-H "$AUTH" \
|
||||||
-F "bucket-id=$BUCKET" \
|
-F "bucket-id=$BUCKET" \
|
||||||
-F "metadata[]={};type=application/json" \
|
|
||||||
-F "file[]=@go.mod" \
|
|
||||||
-F "metadata[]={\"id\":\"7982873d-8e89-4321-ab86-00f80a168c5a\", \"name\":\"config.yaml\",\"metadata\":{\"num\":123,\"list\":[1,2,3]}};type=application/json" \
|
-F "metadata[]={\"id\":\"7982873d-8e89-4321-ab86-00f80a168c5a\", \"name\":\"config.yaml\",\"metadata\":{\"num\":123,\"list\":[1,2,3]}};type=application/json" \
|
||||||
-F "file[]=@storage.yaml" \
|
-F "file[]=@vacuum.yaml" \
|
||||||
-F "metadata[]={\"id\":\"faa80d51-07c7-4268-942d-8f092c98c71a\", \"name\":\"docs.md\"};type=application/json" \
|
-F "metadata[]={\"id\":\"faa80d51-07c7-4268-942d-8f092c98c71a\", \"name\":\"docs.md\"};type=application/json" \
|
||||||
-F "file[]=@README.md" \
|
-F "file[]=@README.md" \
|
||||||
-F "metadata[]={\"id\":\"$FILE_ID\", \"name\":\"logo.jpg\"};type=application/json" \
|
-F "metadata[]={\"id\":\"$FILE_ID\", \"name\":\"logo.jpg\"};type=application/json" \
|
||||||
|
|||||||
3
vendor/github.com/quic-go/quic-go/connection.go
generated
vendored
3
vendor/github.com/quic-go/quic-go/connection.go
generated
vendored
@@ -845,6 +845,9 @@ func (c *Conn) handleHandshakeComplete(now time.Time) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) handleHandshakeConfirmed(now time.Time) error {
|
func (c *Conn) handleHandshakeConfirmed(now time.Time) error {
|
||||||
|
if err := c.dropEncryptionLevel(protocol.EncryptionInitial, now); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := c.dropEncryptionLevel(protocol.EncryptionHandshake, now); err != nil {
|
if err := c.dropEncryptionLevel(protocol.EncryptionHandshake, now); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@@ -811,7 +811,7 @@ github.com/prometheus/procfs/internal/util
|
|||||||
# github.com/quic-go/qpack v0.5.1
|
# github.com/quic-go/qpack v0.5.1
|
||||||
## explicit; go 1.22
|
## explicit; go 1.22
|
||||||
github.com/quic-go/qpack
|
github.com/quic-go/qpack
|
||||||
# github.com/quic-go/quic-go v0.54.0
|
# github.com/quic-go/quic-go v0.54.1
|
||||||
## explicit; go 1.23
|
## explicit; go 1.23
|
||||||
github.com/quic-go/quic-go
|
github.com/quic-go/quic-go
|
||||||
github.com/quic-go/quic-go/http3
|
github.com/quic-go/quic-go/http3
|
||||||
|
|||||||
Reference in New Issue
Block a user