mirror of
https://github.com/fosrl/pangolin.git
synced 2025-11-15 05:47:35 +00:00
Compare commits
2 Commits
copilot/fi
...
1.11.0-s.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1bcdadf80 | ||
|
|
dcb4ae71b8 |
@@ -1725,5 +1725,6 @@
|
||||
"healthCheckNotAvailable": "Local",
|
||||
"rewritePath": "Rewrite Path",
|
||||
"rewritePathDescription": "Optionally rewrite the path before forwarding to the target.",
|
||||
"continueToApplication": "Continue to application"
|
||||
"continueToApplication": "Continue to application",
|
||||
"checkingInvite": "Checking Invite"
|
||||
}
|
||||
|
||||
@@ -35,11 +35,12 @@ function createDb() {
|
||||
}
|
||||
|
||||
// Create connection pools instead of individual connections
|
||||
const poolConfig = config.postgres.pool;
|
||||
const primaryPool = new Pool({
|
||||
connectionString,
|
||||
max: 20,
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 5000,
|
||||
max: poolConfig.max_connections,
|
||||
idleTimeoutMillis: poolConfig.idle_timeout_ms,
|
||||
connectionTimeoutMillis: poolConfig.connection_timeout_ms,
|
||||
});
|
||||
|
||||
const replicas = [];
|
||||
@@ -50,9 +51,9 @@ function createDb() {
|
||||
for (const conn of replicaConnections) {
|
||||
const replicaPool = new Pool({
|
||||
connectionString: conn.connection_string,
|
||||
max: 10,
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 5000,
|
||||
max: poolConfig.max_replica_connections,
|
||||
idleTimeoutMillis: poolConfig.idle_timeout_ms,
|
||||
connectionTimeoutMillis: poolConfig.connection_timeout_ms,
|
||||
});
|
||||
replicas.push(DrizzlePostgres(replicaPool));
|
||||
}
|
||||
|
||||
@@ -158,7 +158,21 @@ export const configSchema = z
|
||||
connection_string: z.string()
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
pool: z
|
||||
.object({
|
||||
max_connections: z.number().positive().optional().default(20),
|
||||
max_replica_connections: z.number().positive().optional().default(10),
|
||||
idle_timeout_ms: z.number().positive().optional().default(30000),
|
||||
connection_timeout_ms: z.number().positive().optional().default(5000)
|
||||
})
|
||||
.optional()
|
||||
.default({
|
||||
max_connections: 20,
|
||||
max_replica_connections: 10,
|
||||
idle_timeout_ms: 30000,
|
||||
connection_timeout_ms: 5000
|
||||
})
|
||||
})
|
||||
.optional(),
|
||||
traefik: z
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { internal } from "@app/lib/api";
|
||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||
import { verifySession } from "@app/lib/auth/verifySession";
|
||||
import { AcceptInviteResponse } from "@server/routers/user";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { redirect } from "next/navigation";
|
||||
import InviteStatusCard from "../../components/InviteStatusCard";
|
||||
import { formatAxiosError } from "@app/lib/api";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function InvitePage(props: {
|
||||
@@ -27,8 +22,8 @@ export default async function InvitePage(props: {
|
||||
if (parts.length !== 2) {
|
||||
return (
|
||||
<>
|
||||
<h1>{t('inviteInvalid')}</h1>
|
||||
<p>{t('inviteInvalidDescription')}</p>
|
||||
<h1>{t("inviteInvalid")}</h1>
|
||||
<p>{t("inviteInvalidDescription")}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -36,58 +31,15 @@ export default async function InvitePage(props: {
|
||||
const inviteId = parts[0];
|
||||
const token = parts[1];
|
||||
|
||||
let error = "";
|
||||
const res = await internal
|
||||
.post<AxiosResponse<AcceptInviteResponse>>(
|
||||
`/invite/accept`,
|
||||
{
|
||||
inviteId,
|
||||
token,
|
||||
},
|
||||
await authCookieHeader()
|
||||
)
|
||||
.catch((e) => {
|
||||
error = formatAxiosError(e);
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
if (res && res.status === 200) {
|
||||
redirect(`/${res.data.data.orgId}`);
|
||||
}
|
||||
|
||||
function cardType() {
|
||||
if (error.includes("Invite is not for this user")) {
|
||||
return "wrong_user";
|
||||
} else if (
|
||||
error.includes("User does not exist. Please create an account first.")
|
||||
) {
|
||||
return "user_does_not_exist";
|
||||
} else if (error.includes("You must be logged in to accept an invite")) {
|
||||
return "not_logged_in";
|
||||
} else {
|
||||
return "rejected";
|
||||
}
|
||||
}
|
||||
|
||||
const type = cardType();
|
||||
|
||||
if (!user && type === "user_does_not_exist") {
|
||||
const redirectUrl = emailParam
|
||||
? `/auth/signup?redirect=/invite?token=${params.token}&email=${encodeURIComponent(emailParam)}`
|
||||
: `/auth/signup?redirect=/invite?token=${params.token}`;
|
||||
redirect(redirectUrl);
|
||||
}
|
||||
|
||||
if (!user && type === "not_logged_in") {
|
||||
const redirectUrl = emailParam
|
||||
? `/auth/login?redirect=/invite?token=${params.token}&email=${encodeURIComponent(emailParam)}`
|
||||
: `/auth/login?redirect=/invite?token=${params.token}`;
|
||||
redirect(redirectUrl);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<InviteStatusCard type={type} token={tokenParam} email={emailParam} />
|
||||
<InviteStatusCard
|
||||
tokenParam={tokenParam}
|
||||
inviteToken={token}
|
||||
inviteId={inviteId}
|
||||
user={user}
|
||||
email={emailParam}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,47 +1,119 @@
|
||||
"use client";
|
||||
|
||||
import { createApiClient } from "@app/lib/api";
|
||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardTitle
|
||||
} from "@app/components/ui/card";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { XCircle } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect, useState } from "react";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { AcceptInviteResponse, GetUserResponse } from "@server/routers/user";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
type InviteStatusCardProps = {
|
||||
type: "rejected" | "wrong_user" | "user_does_not_exist" | "not_logged_in";
|
||||
token: string;
|
||||
user: GetUserResponse | null;
|
||||
tokenParam: string;
|
||||
inviteId: string;
|
||||
inviteToken: string;
|
||||
email?: string;
|
||||
};
|
||||
|
||||
export default function InviteStatusCard({
|
||||
type,
|
||||
token,
|
||||
inviteId,
|
||||
email,
|
||||
user,
|
||||
tokenParam,
|
||||
inviteToken
|
||||
}: InviteStatusCardProps) {
|
||||
const router = useRouter();
|
||||
const api = createApiClient(useEnvContext());
|
||||
const t = useTranslations();
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
const [type, setType] = useState<
|
||||
"rejected" | "wrong_user" | "user_does_not_exist" | "not_logged_in"
|
||||
>("rejected");
|
||||
|
||||
useEffect(() => {
|
||||
async function init() {
|
||||
let error = "";
|
||||
const res = await api
|
||||
.post<AxiosResponse<AcceptInviteResponse>>(`/invite/accept`, {
|
||||
inviteId,
|
||||
token: inviteToken
|
||||
})
|
||||
.catch((e) => {
|
||||
error = formatAxiosError(e);
|
||||
console.log("Error accepting invite:", error);
|
||||
setError(error);
|
||||
// console.error(e);
|
||||
});
|
||||
|
||||
if (res && res.status === 200) {
|
||||
router.push(`/${res.data.data.orgId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
function cardType() {
|
||||
if (error.includes("Invite is not for this user")) {
|
||||
return "wrong_user";
|
||||
} else if (
|
||||
error.includes(
|
||||
"User does not exist. Please create an account first."
|
||||
)
|
||||
) {
|
||||
return "user_does_not_exist";
|
||||
} else if (
|
||||
error.includes("You must be logged in to accept an invite")
|
||||
) {
|
||||
return "not_logged_in";
|
||||
} else {
|
||||
return "rejected";
|
||||
}
|
||||
}
|
||||
|
||||
const type = cardType();
|
||||
setType(type);
|
||||
|
||||
if (!user && type === "user_does_not_exist") {
|
||||
const redirectUrl = email
|
||||
? `/auth/signup?redirect=/invite?token=${tokenParam}&email=${encodeURIComponent(email)}`
|
||||
: `/auth/signup?redirect=/invite?token=${tokenParam}`;
|
||||
router.push(redirectUrl);
|
||||
} else if (!user && type === "not_logged_in") {
|
||||
const redirectUrl = email
|
||||
? `/auth/login?redirect=/invite?token=${tokenParam}&email=${encodeURIComponent(email)}`
|
||||
: `/auth/login?redirect=/invite?token=${tokenParam}`;
|
||||
router.push(redirectUrl);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
}, []);
|
||||
|
||||
async function goToLogin() {
|
||||
await api.post("/auth/logout", {});
|
||||
const redirectUrl = email
|
||||
? `/auth/login?redirect=/invite?token=${token}&email=${encodeURIComponent(email)}`
|
||||
: `/auth/login?redirect=/invite?token=${token}`;
|
||||
const redirectUrl = email
|
||||
? `/auth/login?redirect=/invite?token=${tokenParam}&email=${encodeURIComponent(email)}`
|
||||
: `/auth/login?redirect=/invite?token=${tokenParam}`;
|
||||
router.push(redirectUrl);
|
||||
}
|
||||
|
||||
async function goToSignup() {
|
||||
await api.post("/auth/logout", {});
|
||||
const redirectUrl = email
|
||||
? `/auth/signup?redirect=/invite?token=${token}&email=${encodeURIComponent(email)}`
|
||||
: `/auth/signup?redirect=/invite?token=${token}`;
|
||||
const redirectUrl = email
|
||||
? `/auth/signup?redirect=/invite?token=${tokenParam}&email=${encodeURIComponent(email)}`
|
||||
: `/auth/signup?redirect=/invite?token=${tokenParam}`;
|
||||
router.push(redirectUrl);
|
||||
}
|
||||
|
||||
@@ -50,35 +122,27 @@ export default function InviteStatusCard({
|
||||
return (
|
||||
<div>
|
||||
<p className="text-center mb-4">
|
||||
{t('inviteErrorNotValid')}
|
||||
{t("inviteErrorNotValid")}
|
||||
</p>
|
||||
<ul className="list-disc list-inside text-sm space-y-2">
|
||||
<li>{t('inviteErrorExpired')}</li>
|
||||
<li>{t('inviteErrorRevoked')}</li>
|
||||
<li>{t('inviteErrorTypo')}</li>
|
||||
<li>{t("inviteErrorExpired")}</li>
|
||||
<li>{t("inviteErrorRevoked")}</li>
|
||||
<li>{t("inviteErrorTypo")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
} else if (type === "wrong_user") {
|
||||
return (
|
||||
<div>
|
||||
<p className="text-center mb-4">
|
||||
{t('inviteErrorUser')}
|
||||
</p>
|
||||
<p className="text-center">
|
||||
{t('inviteLoginUser')}
|
||||
</p>
|
||||
<p className="text-center mb-4">{t("inviteErrorUser")}</p>
|
||||
<p className="text-center">{t("inviteLoginUser")}</p>
|
||||
</div>
|
||||
);
|
||||
} else if (type === "user_does_not_exist") {
|
||||
return (
|
||||
<div>
|
||||
<p className="text-center mb-4">
|
||||
{t('inviteErrorNoUser')}
|
||||
</p>
|
||||
<p className="text-center">
|
||||
{t('inviteCreateUser')}
|
||||
</p>
|
||||
<p className="text-center mb-4">{t("inviteErrorNoUser")}</p>
|
||||
<p className="text-center">{t("inviteCreateUser")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -92,37 +156,43 @@ export default function InviteStatusCard({
|
||||
router.push("/");
|
||||
}}
|
||||
>
|
||||
{t('goHome')}
|
||||
{t("goHome")}
|
||||
</Button>
|
||||
);
|
||||
} else if (type === "wrong_user") {
|
||||
return (
|
||||
<Button onClick={goToLogin}>{t('inviteLogInOtherUser')}</Button>
|
||||
<Button onClick={goToLogin}>{t("inviteLogInOtherUser")}</Button>
|
||||
);
|
||||
} else if (type === "user_does_not_exist") {
|
||||
return <Button onClick={goToSignup}>{t('createAnAccount')}</Button>;
|
||||
return <Button onClick={goToSignup}>{t("createAnAccount")}</Button>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-3 md:mt-32 flex items-center justify-center">
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader>
|
||||
{/* <div className="flex items-center justify-center w-20 h-20 rounded-full bg-red-100 mx-auto mb-4">
|
||||
<XCircle
|
||||
className="w-10 h-10 text-red-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div> */}
|
||||
<CardTitle className="text-center text-2xl font-bold">
|
||||
{t('inviteNotAccepted')}
|
||||
{loading ? t("checkingInvite") : t("inviteNotAccepted")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>{renderBody()}</CardContent>
|
||||
<CardContent>
|
||||
{loading && (
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
<span>{t("loading")}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!loading && renderBody()}
|
||||
</CardContent>
|
||||
|
||||
<CardFooter className="flex justify-center space-x-4">
|
||||
{renderFooter()}
|
||||
</CardFooter>
|
||||
{!loading && (
|
||||
<CardFooter className="flex justify-center space-x-4">
|
||||
{renderFooter()}
|
||||
</CardFooter>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user