mirror of
https://github.com/fosrl/pangolin.git
synced 2025-12-18 22:15:35 +00:00
Compare commits
2 Commits
1.13.1
...
1.11.0-s.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1bcdadf80 | ||
|
|
dcb4ae71b8 |
@@ -1725,5 +1725,6 @@
|
|||||||
"healthCheckNotAvailable": "Local",
|
"healthCheckNotAvailable": "Local",
|
||||||
"rewritePath": "Rewrite Path",
|
"rewritePath": "Rewrite Path",
|
||||||
"rewritePathDescription": "Optionally rewrite the path before forwarding to the target.",
|
"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
|
// Create connection pools instead of individual connections
|
||||||
|
const poolConfig = config.postgres.pool;
|
||||||
const primaryPool = new Pool({
|
const primaryPool = new Pool({
|
||||||
connectionString,
|
connectionString,
|
||||||
max: 20,
|
max: poolConfig.max_connections,
|
||||||
idleTimeoutMillis: 30000,
|
idleTimeoutMillis: poolConfig.idle_timeout_ms,
|
||||||
connectionTimeoutMillis: 5000,
|
connectionTimeoutMillis: poolConfig.connection_timeout_ms,
|
||||||
});
|
});
|
||||||
|
|
||||||
const replicas = [];
|
const replicas = [];
|
||||||
@@ -50,9 +51,9 @@ function createDb() {
|
|||||||
for (const conn of replicaConnections) {
|
for (const conn of replicaConnections) {
|
||||||
const replicaPool = new Pool({
|
const replicaPool = new Pool({
|
||||||
connectionString: conn.connection_string,
|
connectionString: conn.connection_string,
|
||||||
max: 10,
|
max: poolConfig.max_replica_connections,
|
||||||
idleTimeoutMillis: 30000,
|
idleTimeoutMillis: poolConfig.idle_timeout_ms,
|
||||||
connectionTimeoutMillis: 5000,
|
connectionTimeoutMillis: poolConfig.connection_timeout_ms,
|
||||||
});
|
});
|
||||||
replicas.push(DrizzlePostgres(replicaPool));
|
replicas.push(DrizzlePostgres(replicaPool));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,21 @@ export const configSchema = z
|
|||||||
connection_string: z.string()
|
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()
|
.optional()
|
||||||
|
.default({
|
||||||
|
max_connections: 20,
|
||||||
|
max_replica_connections: 10,
|
||||||
|
idle_timeout_ms: 30000,
|
||||||
|
connection_timeout_ms: 5000
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
traefik: z
|
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 { verifySession } from "@app/lib/auth/verifySession";
|
||||||
import { AcceptInviteResponse } from "@server/routers/user";
|
|
||||||
import { AxiosResponse } from "axios";
|
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import InviteStatusCard from "../../components/InviteStatusCard";
|
import InviteStatusCard from "../../components/InviteStatusCard";
|
||||||
import { formatAxiosError } from "@app/lib/api";
|
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
|
|
||||||
export default async function InvitePage(props: {
|
export default async function InvitePage(props: {
|
||||||
@@ -27,8 +22,8 @@ export default async function InvitePage(props: {
|
|||||||
if (parts.length !== 2) {
|
if (parts.length !== 2) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>{t('inviteInvalid')}</h1>
|
<h1>{t("inviteInvalid")}</h1>
|
||||||
<p>{t('inviteInvalidDescription')}</p>
|
<p>{t("inviteInvalidDescription")}</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -36,58 +31,15 @@ export default async function InvitePage(props: {
|
|||||||
const inviteId = parts[0];
|
const inviteId = parts[0];
|
||||||
const token = parts[1];
|
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 (
|
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";
|
"use client";
|
||||||
|
|
||||||
import { createApiClient } from "@app/lib/api";
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle
|
||||||
} from "@app/components/ui/card";
|
} from "@app/components/ui/card";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
import { XCircle } from "lucide-react";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useTranslations } from "next-intl";
|
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 InviteStatusCardProps = {
|
||||||
type: "rejected" | "wrong_user" | "user_does_not_exist" | "not_logged_in";
|
user: GetUserResponse | null;
|
||||||
token: string;
|
tokenParam: string;
|
||||||
|
inviteId: string;
|
||||||
|
inviteToken: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function InviteStatusCard({
|
export default function InviteStatusCard({
|
||||||
type,
|
inviteId,
|
||||||
token,
|
|
||||||
email,
|
email,
|
||||||
|
user,
|
||||||
|
tokenParam,
|
||||||
|
inviteToken
|
||||||
}: InviteStatusCardProps) {
|
}: InviteStatusCardProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
const t = useTranslations();
|
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() {
|
async function goToLogin() {
|
||||||
await api.post("/auth/logout", {});
|
await api.post("/auth/logout", {});
|
||||||
const redirectUrl = email
|
const redirectUrl = email
|
||||||
? `/auth/login?redirect=/invite?token=${token}&email=${encodeURIComponent(email)}`
|
? `/auth/login?redirect=/invite?token=${tokenParam}&email=${encodeURIComponent(email)}`
|
||||||
: `/auth/login?redirect=/invite?token=${token}`;
|
: `/auth/login?redirect=/invite?token=${tokenParam}`;
|
||||||
router.push(redirectUrl);
|
router.push(redirectUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function goToSignup() {
|
async function goToSignup() {
|
||||||
await api.post("/auth/logout", {});
|
await api.post("/auth/logout", {});
|
||||||
const redirectUrl = email
|
const redirectUrl = email
|
||||||
? `/auth/signup?redirect=/invite?token=${token}&email=${encodeURIComponent(email)}`
|
? `/auth/signup?redirect=/invite?token=${tokenParam}&email=${encodeURIComponent(email)}`
|
||||||
: `/auth/signup?redirect=/invite?token=${token}`;
|
: `/auth/signup?redirect=/invite?token=${tokenParam}`;
|
||||||
router.push(redirectUrl);
|
router.push(redirectUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,35 +122,27 @@ export default function InviteStatusCard({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-center mb-4">
|
<p className="text-center mb-4">
|
||||||
{t('inviteErrorNotValid')}
|
{t("inviteErrorNotValid")}
|
||||||
</p>
|
</p>
|
||||||
<ul className="list-disc list-inside text-sm space-y-2">
|
<ul className="list-disc list-inside text-sm space-y-2">
|
||||||
<li>{t('inviteErrorExpired')}</li>
|
<li>{t("inviteErrorExpired")}</li>
|
||||||
<li>{t('inviteErrorRevoked')}</li>
|
<li>{t("inviteErrorRevoked")}</li>
|
||||||
<li>{t('inviteErrorTypo')}</li>
|
<li>{t("inviteErrorTypo")}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (type === "wrong_user") {
|
} else if (type === "wrong_user") {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-center mb-4">
|
<p className="text-center mb-4">{t("inviteErrorUser")}</p>
|
||||||
{t('inviteErrorUser')}
|
<p className="text-center">{t("inviteLoginUser")}</p>
|
||||||
</p>
|
|
||||||
<p className="text-center">
|
|
||||||
{t('inviteLoginUser')}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (type === "user_does_not_exist") {
|
} else if (type === "user_does_not_exist") {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-center mb-4">
|
<p className="text-center mb-4">{t("inviteErrorNoUser")}</p>
|
||||||
{t('inviteErrorNoUser')}
|
<p className="text-center">{t("inviteCreateUser")}</p>
|
||||||
</p>
|
|
||||||
<p className="text-center">
|
|
||||||
{t('inviteCreateUser')}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -92,37 +156,43 @@ export default function InviteStatusCard({
|
|||||||
router.push("/");
|
router.push("/");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('goHome')}
|
{t("goHome")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
} else if (type === "wrong_user") {
|
} else if (type === "wrong_user") {
|
||||||
return (
|
return (
|
||||||
<Button onClick={goToLogin}>{t('inviteLogInOtherUser')}</Button>
|
<Button onClick={goToLogin}>{t("inviteLogInOtherUser")}</Button>
|
||||||
);
|
);
|
||||||
} else if (type === "user_does_not_exist") {
|
} else if (type === "user_does_not_exist") {
|
||||||
return <Button onClick={goToSignup}>{t('createAnAccount')}</Button>;
|
return <Button onClick={goToSignup}>{t("createAnAccount")}</Button>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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">
|
<Card className="w-full max-w-md">
|
||||||
<CardHeader>
|
<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">
|
<CardTitle className="text-center text-2xl font-bold">
|
||||||
{t('inviteNotAccepted')}
|
{loading ? t("checkingInvite") : t("inviteNotAccepted")}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</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>
|
||||||
|
|
||||||
|
{!loading && (
|
||||||
<CardFooter className="flex justify-center space-x-4">
|
<CardFooter className="flex justify-center space-x-4">
|
||||||
{renderFooter()}
|
{renderFooter()}
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user