Compare commits
8 Commits
feat/serve
...
fix/multip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
175999c88c | ||
|
|
24e283e22c | ||
|
|
07db7409cb | ||
|
|
346ddc16be | ||
|
|
f9eee53ed0 | ||
|
|
1ab4e24c96 | ||
|
|
34165b390f | ||
|
|
ef0d53db8f |
12
README.md
12
README.md
@@ -47,7 +47,17 @@ See our [roadmap](https://kan.bn/kan/roadmap) for upcoming features.
|
|||||||
|
|
||||||
## Self Hosting 🐳
|
## Self Hosting 🐳
|
||||||
|
|
||||||
The easiest way to self-host Kan is with Docker Compose. This will set up everything for you including your postgres database.
|
### One-click Deployments
|
||||||
|
|
||||||
|
The easiest way to deploy Kan is through Railway. We've partnered with Railway to maintain an official template that supports the development of the project.
|
||||||
|
|
||||||
|
<a href="https://railway.com/deploy/kan?referralCode=bZPsr2&utm_medium=integration&utm_source=template&utm_campaign=generic">
|
||||||
|
<img src="https://railway.app/button.svg" alt="Deploy on Railway" height="40" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
|
||||||
|
Alternatively, you can self-host Kan with Docker Compose. This will set up everything for you including your postgres database.
|
||||||
|
|
||||||
1. Create a new file called `docker-compose.yml` and paste the following configuration:
|
1. Create a new file called `docker-compose.yml` and paste the following configuration:
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.802.0",
|
"@aws-sdk/client-s3": "^3.802.0",
|
||||||
|
"@aws-sdk/lib-storage": "^3.802.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.812.0",
|
"@aws-sdk/s3-request-presigner": "^3.812.0",
|
||||||
"@headlessui/react": "^2.2.0",
|
"@headlessui/react": "^2.2.0",
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
"@tiptap/extension-link": "^2.22.2",
|
"@tiptap/extension-link": "^2.22.2",
|
||||||
"@tiptap/extension-mention": "^3.0.9",
|
"@tiptap/extension-mention": "^3.0.9",
|
||||||
"@tiptap/extension-placeholder": "^2.14.0",
|
"@tiptap/extension-placeholder": "^2.14.0",
|
||||||
|
"@tiptap/extension-typography": "^3.18.0",
|
||||||
"@tiptap/pm": "^2.14.0",
|
"@tiptap/pm": "^2.14.0",
|
||||||
"@tiptap/react": "^2.14.0",
|
"@tiptap/react": "^2.14.0",
|
||||||
"@tiptap/starter-kit": "^2.14.0",
|
"@tiptap/starter-kit": "^2.14.0",
|
||||||
|
|||||||
@@ -361,8 +361,8 @@ export function Auth({ setIsMagicLinkSent, isSignUp }: AuthProps) {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
{!(isCredentialsEnabled || isMagicLinkAvailable) &&
|
||||||
{!isCredentialsEnabled && socialProviders?.length === 0 && (
|
socialProviders?.length === 0 && (
|
||||||
<div className="flex w-full items-center gap-4">
|
<div className="flex w-full items-center gap-4">
|
||||||
<div className="h-[1px] w-1/3 bg-light-600 dark:bg-dark-600" />
|
<div className="h-[1px] w-1/3 bg-light-600 dark:bg-dark-600" />
|
||||||
<span className="text-center text-sm text-light-900 dark:text-dark-900">
|
<span className="text-center text-sm text-light-900 dark:text-dark-900">
|
||||||
@@ -371,65 +371,62 @@ export function Auth({ setIsMagicLinkSent, isSignUp }: AuthProps) {
|
|||||||
<div className="h-[1px] w-1/3 bg-light-600 dark:bg-dark-600" />
|
<div className="h-[1px] w-1/3 bg-light-600 dark:bg-dark-600" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isCredentialsEnabled && socialProviders?.length !== 0 && (
|
{(isCredentialsEnabled || isMagicLinkAvailable) && (
|
||||||
<div className="mb-[1.5rem] flex w-full items-center gap-4">
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="h-[1px] w-full bg-light-600 dark:bg-dark-600" />
|
{socialProviders?.length !== 0 && (
|
||||||
<span className="text-sm text-light-900 dark:text-dark-900">
|
<div className="mb-[1.5rem] flex w-full items-center gap-4">
|
||||||
{t`or`}
|
<div className="h-[1px] w-full bg-light-600 dark:bg-dark-600" />
|
||||||
</span>
|
<span className="text-sm text-light-900 dark:text-dark-900">
|
||||||
<div className="h-[1px] w-full bg-light-600 dark:bg-dark-600" />
|
{t`or`}
|
||||||
</div>
|
</span>
|
||||||
)}
|
<div className="h-[1px] w-full bg-light-600 dark:bg-dark-600" />
|
||||||
<div className="space-y-2">
|
|
||||||
{isSignUp && isCredentialsEnabled && (
|
|
||||||
<div>
|
|
||||||
<Input
|
|
||||||
{...register("name", { required: true })}
|
|
||||||
placeholder={t`Enter your name`}
|
|
||||||
/>
|
|
||||||
{errors.name && (
|
|
||||||
<p className="mt-2 text-xs text-red-400">
|
|
||||||
{t`Please enter a valid name`}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(isCredentialsEnabled || isMagicLinkAvailable) && (
|
<div className="space-y-2">
|
||||||
<>
|
{isSignUp && isCredentialsEnabled && (
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
{...register("email", { required: true })}
|
{...register("name", { required: true })}
|
||||||
placeholder={t`Enter your email address`}
|
placeholder={t`Enter your name`}
|
||||||
/>
|
/>
|
||||||
{errors.email && (
|
{errors.name && (
|
||||||
<p className="mt-2 text-xs text-red-400">
|
<p className="mt-2 text-xs text-red-400">
|
||||||
{t`Please enter a valid email address`}
|
{t`Please enter a valid name`}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
{isCredentialsEnabled && (
|
<div>
|
||||||
<div>
|
<Input
|
||||||
<Input
|
{...register("email", { required: true })}
|
||||||
type="password"
|
placeholder={t`Enter your email address`}
|
||||||
{...register("password", { required: true })}
|
/>
|
||||||
placeholder={t`Enter your password`}
|
{errors.email && (
|
||||||
/>
|
<p className="mt-2 text-xs text-red-400">
|
||||||
{errors.password && (
|
{t`Please enter a valid email address`}
|
||||||
<p className="mt-2 text-xs text-red-400">
|
</p>
|
||||||
{errors.password.message ??
|
|
||||||
t`Please enter a valid password`}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
)}
|
|
||||||
{loginError && (
|
{isCredentialsEnabled && (
|
||||||
<p className="mt-2 text-xs text-red-400">{loginError}</p>
|
<div>
|
||||||
)}
|
<Input
|
||||||
</div>
|
type="password"
|
||||||
{(isCredentialsEnabled || isMagicLinkAvailable) && (
|
{...register("password", { required: true })}
|
||||||
|
placeholder={t`Enter your password`}
|
||||||
|
/>
|
||||||
|
{errors.password && (
|
||||||
|
<p className="mt-2 text-xs text-red-400">
|
||||||
|
{errors.password.message ??
|
||||||
|
t`Please enter a valid password`}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{loginError && (
|
||||||
|
<p className="mt-2 text-xs text-red-400">{loginError}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="mt-[1.5rem] flex items-center gap-4">
|
<div className="mt-[1.5rem] flex items-center gap-4">
|
||||||
<Button
|
<Button
|
||||||
isLoading={isLoginWithEmailPending}
|
isLoading={isLoginWithEmailPending}
|
||||||
@@ -441,8 +438,11 @@ export function Auth({ setIsMagicLinkSent, isSignUp }: AuthProps) {
|
|||||||
{isMagicLinkMode ? t`magic link` : t`email`}
|
{isMagicLinkMode ? t`magic link` : t`email`}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</form>
|
||||||
</form>
|
)}
|
||||||
|
{!(isCredentialsEnabled || isMagicLinkAvailable) && loginError && (
|
||||||
|
<p className="mt-2 text-xs text-red-400">{loginError}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const Avatar = ({
|
|||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const initials = name
|
const initials = name?.trim()
|
||||||
? getInitialsFromName(name)
|
? getInitialsFromName(name)
|
||||||
: inferInitialsFromEmail(email);
|
: inferInitialsFromEmail(email);
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { authClient } from "@kan/auth/client";
|
|||||||
import { useClickOutside } from "~/hooks/useClickOutside";
|
import { useClickOutside } from "~/hooks/useClickOutside";
|
||||||
import { useModal } from "~/providers/modal";
|
import { useModal } from "~/providers/modal";
|
||||||
import { useWorkspace, WorkspaceProvider } from "~/providers/workspace";
|
import { useWorkspace, WorkspaceProvider } from "~/providers/workspace";
|
||||||
|
import { api } from "~/utils/api";
|
||||||
import SideNavigation from "./SideNavigation";
|
import SideNavigation from "./SideNavigation";
|
||||||
|
|
||||||
interface DashboardProps {
|
interface DashboardProps {
|
||||||
@@ -44,6 +45,12 @@ export default function Dashboard({
|
|||||||
const { availableWorkspaces, hasLoaded } = useWorkspace();
|
const { availableWorkspaces, hasLoaded } = useWorkspace();
|
||||||
|
|
||||||
const { data: session, isPending: sessionLoading } = authClient.useSession();
|
const { data: session, isPending: sessionLoading } = authClient.useSession();
|
||||||
|
const { data: user, isLoading: userLoading } = api.user.getUser.useQuery(
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
enabled: !!session?.user,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [isSideNavOpen, setIsSideNavOpen] = useState(false);
|
const [isSideNavOpen, setIsSideNavOpen] = useState(false);
|
||||||
const [isRightPanelOpen, setIsRightPanelOpen] = useState(false);
|
const [isRightPanelOpen, setIsRightPanelOpen] = useState(false);
|
||||||
@@ -155,8 +162,12 @@ export default function Dashboard({
|
|||||||
className={`fixed top-12 z-40 h-[calc(100dvh-3rem)] w-[calc(100vw-1.5rem)] transform transition-transform duration-300 ease-in-out md:relative md:top-0 md:h-full md:w-auto md:translate-x-0 ${isSideNavOpen ? "translate-x-0" : "-translate-x-full md:translate-x-0"} `}
|
className={`fixed top-12 z-40 h-[calc(100dvh-3rem)] w-[calc(100vw-1.5rem)] transform transition-transform duration-300 ease-in-out md:relative md:top-0 md:h-full md:w-auto md:translate-x-0 ${isSideNavOpen ? "translate-x-0" : "-translate-x-full md:translate-x-0"} `}
|
||||||
>
|
>
|
||||||
<SideNavigation
|
<SideNavigation
|
||||||
user={{ displayName: session?.user.name, email: session?.user.email, image: session?.user.image }}
|
user={{
|
||||||
isLoading={sessionLoading}
|
displayName: user?.name ?? session?.user.name,
|
||||||
|
email: user?.email ?? session?.user.email ?? "",
|
||||||
|
image: user?.image ?? undefined,
|
||||||
|
}}
|
||||||
|
isLoading={sessionLoading || userLoading}
|
||||||
onCloseSideNav={closeSideNav}
|
onCloseSideNav={closeSideNav}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
ReactRenderer,
|
ReactRenderer,
|
||||||
useEditor,
|
useEditor,
|
||||||
} from "@tiptap/react";
|
} from "@tiptap/react";
|
||||||
|
import Typography from "@tiptap/extension-typography";
|
||||||
import StarterKit from "@tiptap/starter-kit";
|
import StarterKit from "@tiptap/starter-kit";
|
||||||
import Suggestion from "@tiptap/suggestion";
|
import Suggestion from "@tiptap/suggestion";
|
||||||
import {
|
import {
|
||||||
@@ -514,6 +515,17 @@ export default function Editor({
|
|||||||
return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`;
|
return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
Typography.configure({
|
||||||
|
openDoubleQuote: false,
|
||||||
|
closeDoubleQuote: false,
|
||||||
|
openSingleQuote: false,
|
||||||
|
closeSingleQuote: false,
|
||||||
|
oneHalf: false,
|
||||||
|
oneQuarter: false,
|
||||||
|
threeQuarters: false,
|
||||||
|
superscriptTwo: false,
|
||||||
|
superscriptThree: false,
|
||||||
|
}),
|
||||||
...(enableYouTubeEmbed ? [YouTubeNode] : []),
|
...(enableYouTubeEmbed ? [YouTubeNode] : []),
|
||||||
],
|
],
|
||||||
content,
|
content,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import tippy from "tippy.js";
|
|||||||
|
|
||||||
interface TooltipProps {
|
interface TooltipProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
content: ReactNode;
|
content?: ReactNode;
|
||||||
placement?: Placement;
|
placement?: Placement;
|
||||||
delay?: number | [number, number];
|
delay?: number | [number, number];
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,8 @@ export function Tooltip({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!triggerRef.current) return;
|
if (!triggerRef.current) return;
|
||||||
|
|
||||||
|
if (!content) return;
|
||||||
|
|
||||||
const container = document.createElement("div");
|
const container = document.createElement("div");
|
||||||
const root = createRoot(container);
|
const root = createRoot(container);
|
||||||
rootRef.current = root;
|
rootRef.current = root;
|
||||||
|
|||||||
131
apps/web/src/pages/api/upload/attachment.ts
Normal file
131
apps/web/src/pages/api/upload/attachment.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { Upload } from "@aws-sdk/lib-storage";
|
||||||
|
|
||||||
|
import { createNextApiContext } from "@kan/api/trpc";
|
||||||
|
import * as cardRepo from "@kan/db/repository/card.repo";
|
||||||
|
import * as cardActivityRepo from "@kan/db/repository/cardActivity.repo";
|
||||||
|
import * as cardAttachmentRepo from "@kan/db/repository/cardAttachment.repo";
|
||||||
|
import { generateUID } from "@kan/shared/utils";
|
||||||
|
|
||||||
|
import { env } from "~/env";
|
||||||
|
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||||
|
import { createS3Client } from "@kan/shared/utils";
|
||||||
|
import { assertPermission } from "@kan/api/utils/permissions";
|
||||||
|
|
||||||
|
const MAX_SIZE_BYTES = 50 * 1024 * 1024; // 50MB
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withRateLimit(
|
||||||
|
{ points: 100, duration: 60 },
|
||||||
|
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
if (req.method !== "POST") {
|
||||||
|
return res.status(405).json({ error: "Method not allowed" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { user, db } = await createNextApiContext(req);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(401).json({ error: "Unauthorized" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const bucket = env.NEXT_PUBLIC_ATTACHMENTS_BUCKET_NAME;
|
||||||
|
if (!bucket) {
|
||||||
|
return res.status(500).json({ error: "Attachments bucket not configured" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardPublicId = req.query.cardPublicId;
|
||||||
|
if (typeof cardPublicId !== "string" || cardPublicId.length < 12) {
|
||||||
|
return res.status(400).json({ error: "Invalid cardPublicId" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = req.headers["content-type"];
|
||||||
|
const contentLengthHeader = req.headers["content-length"];
|
||||||
|
const contentLength = contentLengthHeader
|
||||||
|
? Number.parseInt(contentLengthHeader, 10)
|
||||||
|
: NaN;
|
||||||
|
|
||||||
|
if (typeof contentType !== "string") {
|
||||||
|
return res.status(400).json({ error: "Missing content type" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Number.isFinite(contentLength) || contentLength <= 0) {
|
||||||
|
return res.status(400).json({ error: "Missing or invalid content length" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentLength > MAX_SIZE_BYTES) {
|
||||||
|
return res.status(400).json({ error: "File too large" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalFilenameHeader =
|
||||||
|
(req.headers["x-original-filename"] as string | undefined) ?? "file";
|
||||||
|
|
||||||
|
const sanitizedFilename = originalFilenameHeader
|
||||||
|
.replace(/[^a-zA-Z0-9._-]/g, "_")
|
||||||
|
.substring(0, 200);
|
||||||
|
|
||||||
|
// Get card and check permissions
|
||||||
|
const card = await cardRepo.getWorkspaceAndCardIdByCardPublicId(
|
||||||
|
db,
|
||||||
|
cardPublicId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!card) {
|
||||||
|
return res.status(404).json({ error: "Card not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has permission to edit the card
|
||||||
|
try {
|
||||||
|
await assertPermission(db, user.id, card.workspaceId, "card:edit");
|
||||||
|
} catch {
|
||||||
|
return res.status(403).json({ error: "Permission denied" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const s3Key = `${card.workspaceId}/${cardPublicId}/${generateUID()}-${sanitizedFilename}`;
|
||||||
|
|
||||||
|
const client = createS3Client();
|
||||||
|
|
||||||
|
const upload = new Upload({
|
||||||
|
client,
|
||||||
|
params: {
|
||||||
|
Bucket: bucket,
|
||||||
|
Key: s3Key,
|
||||||
|
Body: req,
|
||||||
|
ContentType: contentType,
|
||||||
|
ContentLength: contentLength,
|
||||||
|
},
|
||||||
|
leavePartsOnError: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await upload.done();
|
||||||
|
|
||||||
|
// Create attachment record and log activity
|
||||||
|
const attachment = await cardAttachmentRepo.create(db, {
|
||||||
|
cardId: card.id,
|
||||||
|
filename: sanitizedFilename,
|
||||||
|
originalFilename: originalFilenameHeader,
|
||||||
|
contentType,
|
||||||
|
size: contentLength,
|
||||||
|
s3Key,
|
||||||
|
createdBy: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await cardActivityRepo.create(db, {
|
||||||
|
type: "card.updated.attachment.added",
|
||||||
|
cardId: card.id,
|
||||||
|
createdBy: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ attachment });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Attachment upload failed", error);
|
||||||
|
return res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
101
apps/web/src/pages/api/upload/avatar.ts
Normal file
101
apps/web/src/pages/api/upload/avatar.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { PutObjectCommand } from "@aws-sdk/client-s3";
|
||||||
|
|
||||||
|
import { createNextApiContext } from "@kan/api/trpc";
|
||||||
|
import * as userRepo from "@kan/db/repository/user.repo";
|
||||||
|
|
||||||
|
import { env } from "~/env";
|
||||||
|
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
||||||
|
import { createS3Client } from "@kan/shared/utils";
|
||||||
|
|
||||||
|
const MAX_SIZE_BYTES = 2 * 1024 * 1024; // 2MB
|
||||||
|
const allowedContentTypes = ["image/jpeg", "image/png", "image/webp"];
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withRateLimit(
|
||||||
|
{ points: 100, duration: 60 },
|
||||||
|
async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
if (req.method !== "POST") {
|
||||||
|
return res.status(405).json({ error: "Method not allowed" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { user, db } = await createNextApiContext(req);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(401).json({ error: "Unauthorized" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const bucket = env.NEXT_PUBLIC_AVATAR_BUCKET_NAME;
|
||||||
|
if (!bucket) {
|
||||||
|
return res.status(500).json({ error: "Avatar bucket not configured" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = req.headers["content-type"];
|
||||||
|
const contentLengthHeader = req.headers["content-length"];
|
||||||
|
const contentLength = contentLengthHeader
|
||||||
|
? Number.parseInt(contentLengthHeader, 10)
|
||||||
|
: NaN;
|
||||||
|
|
||||||
|
if (typeof contentType !== "string") {
|
||||||
|
return res.status(400).json({ error: "Missing content type" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allowedContentTypes.includes(contentType)) {
|
||||||
|
return res.status(400).json({ error: "Invalid content type" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Number.isFinite(contentLength) || contentLength <= 0) {
|
||||||
|
return res.status(400).json({ error: "Missing or invalid content length" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentLength > MAX_SIZE_BYTES) {
|
||||||
|
return res.status(400).json({ error: "File too large" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalFilenameHeader =
|
||||||
|
(req.headers["x-original-filename"] as string | undefined) ?? "file";
|
||||||
|
|
||||||
|
const sanitizedFilename = originalFilenameHeader
|
||||||
|
.replace(/[^a-zA-Z0-9._-]/g, "_")
|
||||||
|
.substring(0, 200);
|
||||||
|
|
||||||
|
const s3Key = `${user.id}/${sanitizedFilename}`;
|
||||||
|
|
||||||
|
const client = createS3Client();
|
||||||
|
|
||||||
|
// Upload the file to S3
|
||||||
|
await client.send(
|
||||||
|
new PutObjectCommand({
|
||||||
|
Bucket: bucket,
|
||||||
|
Key: s3Key,
|
||||||
|
Body: req,
|
||||||
|
ContentType: contentType,
|
||||||
|
ContentLength: contentLength,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update user image in database
|
||||||
|
const updatedUser = await userRepo.update(db, user.id, {
|
||||||
|
image: s3Key,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
key: s3Key,
|
||||||
|
filename: sanitizedFilename,
|
||||||
|
contentType,
|
||||||
|
size: contentLength,
|
||||||
|
user: updatedUser,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Avatar upload failed", error);
|
||||||
|
return res.status(500).json({ error: "Internal server error" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
||||||
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
||||||
import { env as nextRuntimeEnv } from "next-runtime-env";
|
|
||||||
|
|
||||||
import { createNextApiContext } from "@kan/api/trpc";
|
|
||||||
|
|
||||||
import { env } from "~/env";
|
|
||||||
import { withRateLimit } from "@kan/api/utils/rateLimit";
|
|
||||||
|
|
||||||
const allowedContentTypes = ["image/jpeg", "image/png"];
|
|
||||||
|
|
||||||
export default withRateLimit(
|
|
||||||
{ points: 100, duration: 60 },
|
|
||||||
async (req: NextApiRequest, res: NextApiResponse) => {
|
|
||||||
if (req.method !== "POST") {
|
|
||||||
return res.status(405).json({ error: "Method not allowed" });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { user } = await createNextApiContext(req);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return res.status(401).json({ error: "Unauthorized" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { filename, contentType } = req.body as {
|
|
||||||
filename: string;
|
|
||||||
contentType: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Specific to avatar uploads for now
|
|
||||||
const filenameRegex = /^[a-f0-9\-]+\/[a-zA-Z0-9_\-]+(\.jpg|\.jpeg|\.png)$/;
|
|
||||||
|
|
||||||
if (!filenameRegex.test(filename)) {
|
|
||||||
return res.status(400).json({ error: "Invalid filename" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof contentType !== "string" ||
|
|
||||||
!allowedContentTypes.includes(contentType)
|
|
||||||
) {
|
|
||||||
return res.status(400).json({ error: "Invalid content type" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const credentials =
|
|
||||||
env.S3_ACCESS_KEY_ID && env.S3_SECRET_ACCESS_KEY
|
|
||||||
? {
|
|
||||||
accessKeyId: env.S3_ACCESS_KEY_ID,
|
|
||||||
secretAccessKey: env.S3_SECRET_ACCESS_KEY,
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const client = new S3Client({
|
|
||||||
region: env.S3_REGION ?? "",
|
|
||||||
endpoint: env.S3_ENDPOINT ?? "",
|
|
||||||
forcePathStyle: env.S3_FORCE_PATH_STYLE === "true",
|
|
||||||
credentials,
|
|
||||||
});
|
|
||||||
|
|
||||||
const signedUrl = await getSignedUrl(
|
|
||||||
client,
|
|
||||||
new PutObjectCommand({
|
|
||||||
Bucket: nextRuntimeEnv("NEXT_PUBLIC_AVATAR_BUCKET_NAME") ?? "",
|
|
||||||
Key: filename,
|
|
||||||
ACL: "public-read",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).json({ url: signedUrl, key: filename });
|
|
||||||
} catch (error) {
|
|
||||||
return res.status(500).json({ error: (error as Error).message });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import { env } from "next-runtime-env";
|
|
||||||
|
|
||||||
export const formatToArray = (
|
export const formatToArray = (
|
||||||
value: string | string[] | undefined,
|
value: string | string[] | undefined,
|
||||||
): string[] => {
|
): string[] => {
|
||||||
@@ -52,14 +50,5 @@ export const getAvatarUrl = (imageOrKey: string | null) => {
|
|||||||
return imageOrKey;
|
return imageOrKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bucket = env("NEXT_PUBLIC_AVATAR_BUCKET_NAME");
|
return "";
|
||||||
const useVirtualHostedUrls = env("NEXT_PUBLIC_USE_VIRTUAL_HOSTED_URLS");
|
|
||||||
const storageDomain = env("NEXT_PUBLIC_STORAGE_DOMAIN");
|
|
||||||
|
|
||||||
if (useVirtualHostedUrls === "true" && storageDomain) {
|
|
||||||
return `https://${bucket}.${storageDomain}/${imageOrKey}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const storageUrl = env("NEXT_PUBLIC_STORAGE_URL");
|
|
||||||
return `${storageUrl}/${bucket}/${imageOrKey}`;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,24 +1,40 @@
|
|||||||
import Link from "next/link";
|
import { Fragment } from "react";
|
||||||
import { t } from "@lingui/core/macro";
|
import { t } from "@lingui/core/macro";
|
||||||
import { env } from "next-runtime-env";
|
import { env } from "next-runtime-env";
|
||||||
import { HiLink } from "react-icons/hi";
|
import { HiLink } from "react-icons/hi";
|
||||||
|
|
||||||
import { Tooltip } from "~/components/Tooltip";
|
import { Tooltip } from "~/components/Tooltip";
|
||||||
|
import { usePopup } from "~/providers/popup";
|
||||||
|
|
||||||
|
const displayBaseUrl =
|
||||||
|
env("NEXT_PUBLIC_KAN_ENV") === "cloud"
|
||||||
|
? "kan.bn"
|
||||||
|
: env("NEXT_PUBLIC_BASE_URL");
|
||||||
|
|
||||||
|
const linkBaseUrl = env("NEXT_PUBLIC_BASE_URL");
|
||||||
|
|
||||||
|
const pathSeparator = (
|
||||||
|
<div className="mx-1.5 h-4 w-px rotate-[20deg] bg-gray-300 dark:bg-dark-600" />
|
||||||
|
);
|
||||||
|
|
||||||
const UpdateBoardSlugButton = ({
|
const UpdateBoardSlugButton = ({
|
||||||
handleOnClick,
|
handleOnClick,
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
boardSlug,
|
boardSlug,
|
||||||
|
boardPublicId,
|
||||||
|
visibility,
|
||||||
isLoading,
|
isLoading,
|
||||||
canEdit,
|
canEdit,
|
||||||
}: {
|
}: {
|
||||||
handleOnClick: () => void;
|
handleOnClick: () => void;
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
boardSlug: string;
|
boardSlug: string;
|
||||||
|
boardPublicId: string;
|
||||||
|
visibility: "public" | "private";
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
canEdit: boolean;
|
canEdit: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
if (!isLoading && (!workspaceSlug || !boardSlug)) return <></>;
|
const { showPopup } = usePopup();
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -26,44 +42,55 @@ const UpdateBoardSlugButton = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!workspaceSlug || !boardSlug || !boardPublicId) return <></>;
|
||||||
|
|
||||||
|
const isPublic = visibility === "public";
|
||||||
|
const boardUrl = isPublic
|
||||||
|
? `${linkBaseUrl}/${workspaceSlug}/${boardSlug}`
|
||||||
|
: `${linkBaseUrl}/boards/${boardPublicId}`;
|
||||||
|
|
||||||
|
const pathSegments = isPublic
|
||||||
|
? [displayBaseUrl, workspaceSlug, boardSlug]
|
||||||
|
: [displayBaseUrl, "boards", boardPublicId];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={!canEdit && !isLoading ? t`You don't have permission` : undefined}
|
content={!canEdit ? t`You don't have permission` : undefined}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
onClick={canEdit ? handleOnClick : undefined}
|
onClick={canEdit ? handleOnClick : undefined}
|
||||||
disabled={!canEdit || isLoading}
|
disabled={!canEdit || isLoading}
|
||||||
className="hidden cursor-pointer items-center gap-2 rounded-full border-[1px] bg-light-50 p-1 pl-4 pr-1 text-sm text-light-950 hover:bg-light-100 disabled:cursor-not-allowed disabled:opacity-60 dark:border-dark-600 dark:bg-dark-50 dark:text-dark-900 dark:hover:bg-dark-100 xl:flex"
|
className="hidden cursor-pointer items-center gap-2 rounded-full border-[1px] bg-light-50 p-1 pl-4 pr-1 text-sm text-light-950 hover:bg-light-100 disabled:cursor-not-allowed disabled:opacity-60 dark:border-dark-600 dark:bg-dark-50 dark:text-dark-900 dark:hover:bg-dark-100 xl:flex"
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span>
|
{pathSegments.map((segment, i) => (
|
||||||
{env("NEXT_PUBLIC_KAN_ENV") === "cloud"
|
<Fragment key={i}>
|
||||||
? "kan.bn"
|
{i > 0 && pathSeparator}
|
||||||
: env("NEXT_PUBLIC_BASE_URL")}
|
<span>{segment}</span>
|
||||||
</span>
|
</Fragment>
|
||||||
<div className="mx-1.5 h-4 w-px rotate-[20deg] bg-gray-300 dark:bg-dark-600"></div>
|
))}
|
||||||
<span>{workspaceSlug}</span>
|
</div>
|
||||||
<div className="mx-1.5 h-4 w-px rotate-[20deg] bg-gray-300 dark:bg-dark-600"></div>
|
<button
|
||||||
<span>{boardSlug}</span>
|
type="button"
|
||||||
</div>
|
onClick={(e) => {
|
||||||
<Link
|
e.stopPropagation();
|
||||||
href={`${env("NEXT_PUBLIC_BASE_URL")}/${workspaceSlug}/${boardSlug}`}
|
navigator.clipboard.writeText(boardUrl).then(
|
||||||
target="_blank"
|
() =>
|
||||||
rel="noopener noreferrer"
|
showPopup({
|
||||||
onClick={(e) => {
|
header: t`Link copied`,
|
||||||
e.stopPropagation();
|
icon: "success",
|
||||||
if (!canEdit) {
|
message: t`Board URL copied to clipboard`,
|
||||||
e.preventDefault();
|
}),
|
||||||
}
|
).catch(() => undefined);
|
||||||
}}
|
}}
|
||||||
className="flex h-7 w-7 items-center justify-center rounded-full hover:bg-light-200 dark:hover:bg-dark-200"
|
className="flex h-7 w-7 items-center justify-center rounded-full hover:bg-light-200 dark:hover:bg-dark-200"
|
||||||
>
|
aria-label={t`Copy board link`}
|
||||||
<HiLink className="h-[13px] w-[13px]" />
|
>
|
||||||
</Link>
|
<HiLink className="h-[13px] w-[13px]" />
|
||||||
</button>
|
</button>
|
||||||
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default UpdateBoardSlugButton;
|
export default UpdateBoardSlugButton;
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ export function UpdateBoardSlugForm({
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
href="/settings?tab=workspace"
|
href="/settings/workspace"
|
||||||
onClick={closeModal}
|
onClick={closeModal}
|
||||||
>
|
>
|
||||||
{t`Edit workspace URL`}
|
{t`Edit workspace URL`}
|
||||||
|
|||||||
@@ -443,6 +443,8 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
workspaceSlug={workspace.slug ?? ""}
|
workspaceSlug={workspace.slug ?? ""}
|
||||||
boardSlug={boardData?.slug ?? ""}
|
boardSlug={boardData?.slug ?? ""}
|
||||||
|
boardPublicId={boardId ?? ""}
|
||||||
|
visibility={boardData?.visibility ?? "private"}
|
||||||
canEdit={canEditBoard}
|
canEdit={canEditBoard}
|
||||||
/>
|
/>
|
||||||
<VisibilityButton
|
<VisibilityButton
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { twMerge } from "tailwind-merge";
|
|||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
import { useModal } from "~/providers/modal";
|
import { useModal } from "~/providers/modal";
|
||||||
import { usePopup } from "~/providers/popup";
|
import { usePopup } from "~/providers/popup";
|
||||||
|
import { env } from "next-runtime-env";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { invalidateCard } from "~/utils/cardInvalidation";
|
import { invalidateCard } from "~/utils/cardInvalidation";
|
||||||
|
|
||||||
@@ -18,62 +19,33 @@ export function AttachmentUpload({ cardPublicId }: { cardPublicId: string }) {
|
|||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
const generateUploadUrl = api.attachment.generateUploadUrl.useMutation();
|
const uploadFile = async (file: File) => {
|
||||||
const confirmAttachment = api.attachment.confirm.useMutation({
|
setUploading(true);
|
||||||
onSuccess: async () => {
|
|
||||||
|
try {
|
||||||
|
const baseUrl = env("NEXT_PUBLIC_BASE_URL") ?? "";
|
||||||
|
const response = await fetch(
|
||||||
|
`${baseUrl}/api/upload/attachment?cardPublicId=${encodeURIComponent(cardPublicId)}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": file.type,
|
||||||
|
"x-original-filename": file.name,
|
||||||
|
},
|
||||||
|
body: file,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Upload failed");
|
||||||
|
}
|
||||||
|
|
||||||
await invalidateCard(utils, cardPublicId);
|
await invalidateCard(utils, cardPublicId);
|
||||||
showPopup({
|
showPopup({
|
||||||
header: t`Attachment uploaded`,
|
header: t`Attachment uploaded`,
|
||||||
message: t`Your file has been uploaded successfully.`,
|
message: t`Your file has been uploaded successfully.`,
|
||||||
icon: "success",
|
icon: "success",
|
||||||
});
|
});
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
showPopup({
|
|
||||||
header: t`Upload failed`,
|
|
||||||
message: t`Failed to upload attachment. Please try again.`,
|
|
||||||
icon: "error",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSettled: () => {
|
|
||||||
setUploading(false);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const uploadFile = async (file: File) => {
|
|
||||||
setUploading(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Generate presigned URL
|
|
||||||
const { url, key } = await generateUploadUrl.mutateAsync({
|
|
||||||
cardPublicId,
|
|
||||||
filename: file.name,
|
|
||||||
contentType: file.type,
|
|
||||||
size: file.size,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Upload file to S3
|
|
||||||
const uploadResponse = await fetch(url, {
|
|
||||||
method: "PUT",
|
|
||||||
body: file,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": file.type,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!uploadResponse.ok) {
|
|
||||||
throw new Error("Upload failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm attachment in database
|
|
||||||
await confirmAttachment.mutateAsync({
|
|
||||||
cardPublicId,
|
|
||||||
s3Key: key,
|
|
||||||
filename: file.name,
|
|
||||||
originalFilename: file.name,
|
|
||||||
contentType: file.type,
|
|
||||||
size: file.size,
|
|
||||||
});
|
|
||||||
} catch {
|
} catch {
|
||||||
showPopup({
|
showPopup({
|
||||||
header: t`Upload failed`,
|
header: t`Upload failed`,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { t } from "@lingui/core/macro";
|
import { t } from "@lingui/core/macro";
|
||||||
import {
|
import {
|
||||||
HiEllipsisHorizontal,
|
HiEllipsisHorizontal,
|
||||||
|
HiLink,
|
||||||
HiOutlineCheckCircle,
|
HiOutlineCheckCircle,
|
||||||
HiOutlineTrash,
|
HiOutlineTrash,
|
||||||
} from "react-icons/hi2";
|
} from "react-icons/hi2";
|
||||||
@@ -10,18 +11,54 @@ import { authClient } from "@kan/auth/client";
|
|||||||
import Dropdown from "~/components/Dropdown";
|
import Dropdown from "~/components/Dropdown";
|
||||||
import { usePermissions } from "~/hooks/usePermissions";
|
import { usePermissions } from "~/hooks/usePermissions";
|
||||||
import { useModal } from "~/providers/modal";
|
import { useModal } from "~/providers/modal";
|
||||||
|
import { usePopup } from "~/providers/popup";
|
||||||
|
|
||||||
export default function CardDropdown({
|
export default function CardDropdown({
|
||||||
|
cardPublicId,
|
||||||
|
isTemplate,
|
||||||
|
boardPublicId,
|
||||||
cardCreatedBy,
|
cardCreatedBy,
|
||||||
}: {
|
}: {
|
||||||
|
cardPublicId: string;
|
||||||
|
isTemplate?: boolean;
|
||||||
|
boardPublicId?: string;
|
||||||
cardCreatedBy?: string | null;
|
cardCreatedBy?: string | null;
|
||||||
}) {
|
}) {
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
const { showPopup } = usePopup();
|
||||||
const { canEditCard, canDeleteCard } = usePermissions();
|
const { canEditCard, canDeleteCard } = usePermissions();
|
||||||
const { data: session } = authClient.useSession();
|
const { data: session } = authClient.useSession();
|
||||||
const isCreator = cardCreatedBy && session?.user.id === cardCreatedBy;
|
const isCreator = cardCreatedBy && session?.user.id === cardCreatedBy;
|
||||||
|
|
||||||
|
const handleCopyCardLink = async () => {
|
||||||
|
const path =
|
||||||
|
isTemplate && boardPublicId
|
||||||
|
? `/templates/${boardPublicId}/cards/${cardPublicId}`
|
||||||
|
: `/cards/${cardPublicId}`;
|
||||||
|
const url = `${window.location.origin}${path}`;
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(url);
|
||||||
|
showPopup({
|
||||||
|
header: t`Link copied`,
|
||||||
|
icon: "success",
|
||||||
|
message: t`Card URL copied to clipboard`,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
showPopup({
|
||||||
|
header: t`Unable to copy link`,
|
||||||
|
icon: "error",
|
||||||
|
message: t`Please try again.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
|
{
|
||||||
|
label: t`Copy card link`,
|
||||||
|
action: handleCopyCardLink,
|
||||||
|
icon: <HiLink className="h-[16px] w-[16px] text-dark-900" />,
|
||||||
|
},
|
||||||
...(canEditCard
|
...(canEditCard
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { useEffect, useState } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { IoChevronForwardSharp } from "react-icons/io5";
|
import { IoChevronForwardSharp } from "react-icons/io5";
|
||||||
|
|
||||||
|
import { authClient } from "@kan/auth/client";
|
||||||
|
|
||||||
import Avatar from "~/components/Avatar";
|
import Avatar from "~/components/Avatar";
|
||||||
import Editor from "~/components/Editor";
|
import Editor from "~/components/Editor";
|
||||||
import FeedbackModal from "~/components/FeedbackModal";
|
import FeedbackModal from "~/components/FeedbackModal";
|
||||||
@@ -14,8 +16,6 @@ import Modal from "~/components/modal";
|
|||||||
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
|
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
import { EditYouTubeModal } from "~/components/YouTubeEmbed/EditYouTubeModal";
|
import { EditYouTubeModal } from "~/components/YouTubeEmbed/EditYouTubeModal";
|
||||||
import { authClient } from "@kan/auth/client";
|
|
||||||
|
|
||||||
import { usePermissions } from "~/hooks/usePermissions";
|
import { usePermissions } from "~/hooks/usePermissions";
|
||||||
import { useModal } from "~/providers/modal";
|
import { useModal } from "~/providers/modal";
|
||||||
import { usePopup } from "~/providers/popup";
|
import { usePopup } from "~/providers/popup";
|
||||||
@@ -317,7 +317,12 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Dropdown cardCreatedBy={card?.createdBy} />
|
<Dropdown
|
||||||
|
cardPublicId={cardId}
|
||||||
|
isTemplate={isTemplate}
|
||||||
|
boardPublicId={boardId}
|
||||||
|
cardCreatedBy={card?.createdBy}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -374,8 +379,14 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
|
|||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<Editor
|
<Editor
|
||||||
content={card.description}
|
content={card.description}
|
||||||
onChange={canEdit ? (e) => setValue("description", e) : undefined}
|
onChange={
|
||||||
onBlur={canEdit ? () => handleSubmit(onSubmit)() : undefined}
|
canEdit
|
||||||
|
? (e) => setValue("description", e)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onBlur={
|
||||||
|
canEdit ? () => handleSubmit(onSubmit)() : undefined
|
||||||
|
}
|
||||||
workspaceMembers={board?.workspace.members ?? []}
|
workspaceMembers={board?.workspace.members ?? []}
|
||||||
readOnly={!canEdit}
|
readOnly={!canEdit}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ export default function MembersPage() {
|
|||||||
name={memberName ?? ""}
|
name={memberName ?? ""}
|
||||||
email={memberEmail ?? ""}
|
email={memberEmail ?? ""}
|
||||||
imageUrl={memberImage ? getAvatarUrl(memberImage) : undefined}
|
imageUrl={memberImage ? getAvatarUrl(memberImage) : undefined}
|
||||||
icon={showPendingIcon ? "?" : undefined}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { t } from "@lingui/core/macro";
|
import { t } from "@lingui/core/macro";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { HiXMark } from "react-icons/hi2";
|
import { HiLink, HiXMark } from "react-icons/hi2";
|
||||||
|
|
||||||
import Badge from "~/components/Badge";
|
import Badge from "~/components/Badge";
|
||||||
import Editor from "~/components/Editor";
|
import Editor from "~/components/Editor";
|
||||||
import LabelIcon from "~/components/LabelIcon";
|
import LabelIcon from "~/components/LabelIcon";
|
||||||
import { useModal } from "~/providers/modal";
|
import { useModal } from "~/providers/modal";
|
||||||
|
import { usePopup } from "~/providers/popup";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import ActivityList from "~/views/card/components/ActivityList";
|
import ActivityList from "~/views/card/components/ActivityList";
|
||||||
import { AttachmentThumbnails } from "~/views/card/components/AttachmentThumbnails";
|
import { AttachmentThumbnails } from "~/views/card/components/AttachmentThumbnails";
|
||||||
@@ -23,10 +24,29 @@ export function CardModal({
|
|||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { closeModal, isOpen } = useModal();
|
const { closeModal, isOpen } = useModal();
|
||||||
|
const { showPopup } = usePopup();
|
||||||
const [showFade, setShowFade] = useState(false);
|
const [showFade, setShowFade] = useState(false);
|
||||||
const [showTopFade, setShowTopFade] = useState(false);
|
const [showTopFade, setShowTopFade] = useState(false);
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const handleCopyCardLink = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(window.location.href);
|
||||||
|
showPopup({
|
||||||
|
header: t`Link copied`,
|
||||||
|
icon: "success",
|
||||||
|
message: t`Card URL copied to clipboard`,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
showPopup({
|
||||||
|
header: t`Unable to copy link`,
|
||||||
|
icon: "error",
|
||||||
|
message: t`Please try again.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const { data, isLoading } = api.card.byId.useQuery(
|
const { data, isLoading } = api.card.byId.useQuery(
|
||||||
{
|
{
|
||||||
cardPublicId: cardPublicId ?? "",
|
cardPublicId: cardPublicId ?? "",
|
||||||
@@ -65,33 +85,44 @@ export function CardModal({
|
|||||||
<div className="h-full p-8">
|
<div className="h-full p-8">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<div className="flex w-full items-center justify-between">
|
<div className="flex w-full items-center justify-between">
|
||||||
<button
|
<div className="absolute right-[2rem] top-[2rem] flex items-center gap-1">
|
||||||
className="absolute right-[2rem] top-[2rem] rounded p-1 hover:bg-light-300 focus:outline-none dark:hover:bg-dark-300"
|
<button
|
||||||
onClick={(e) => {
|
type="button"
|
||||||
e.preventDefault();
|
onClick={handleCopyCardLink}
|
||||||
closeModal();
|
className="rounded p-1.5 transition-all hover:bg-light-200 focus:outline-none dark:hover:bg-dark-100"
|
||||||
|
aria-label="Copy card link"
|
||||||
|
>
|
||||||
|
<HiLink className="h-4 w-4 text-light-900 dark:text-dark-900" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded p-1.5 transition-all hover:bg-light-200 focus:outline-none dark:hover:bg-dark-100"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
closeModal();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
void router.replace(
|
void router.replace(
|
||||||
{
|
{
|
||||||
pathname: router.pathname,
|
pathname: router.pathname,
|
||||||
query: {
|
query: {
|
||||||
...router.query,
|
...router.query,
|
||||||
workspaceSlug,
|
workspaceSlug: workspaceSlug ?? "",
|
||||||
boardSlug: [boardSlug],
|
boardSlug: [boardSlug ?? ""],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
undefined,
|
||||||
undefined,
|
{ shallow: true },
|
||||||
{ shallow: true },
|
);
|
||||||
);
|
}, 400);
|
||||||
}, 400);
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<HiXMark
|
||||||
<HiXMark
|
size={18}
|
||||||
size={18}
|
className="text-light-900 dark:text-dark-900"
|
||||||
className="dark:text-dark-9000 text-light-900"
|
/>
|
||||||
/>
|
</button>
|
||||||
</button>
|
</div>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<div className="h-[2.3rem] w-[300px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
|
<div className="h-[2.3rem] w-[300px] animate-pulse rounded-[5px] bg-light-300 dark:bg-dark-300" />
|
||||||
|
|||||||
@@ -70,30 +70,35 @@ export default function PublicBoardView() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const CopyBoardLink = () => {
|
const handleCopyBoardLink = async () => {
|
||||||
return (
|
try {
|
||||||
<button
|
await navigator.clipboard.writeText(window.location.href);
|
||||||
onClick={async () => {
|
showPopup({
|
||||||
try {
|
header: t`Link copied`,
|
||||||
await navigator.clipboard.writeText(window.location.href);
|
icon: "success",
|
||||||
} catch (error) {
|
message: t`Board URL copied to clipboard`,
|
||||||
console.error(error);
|
});
|
||||||
}
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
showPopup({
|
showPopup({
|
||||||
header: t`Link copied`,
|
header: t`Unable to copy link`,
|
||||||
icon: "success",
|
icon: "error",
|
||||||
message: t`Board URL copied to clipboard`,
|
message: t`Please try again.`,
|
||||||
});
|
});
|
||||||
}}
|
}
|
||||||
className="rounded p-1.5 transition-all hover:bg-light-200 dark:hover:bg-dark-100"
|
|
||||||
aria-label={`Copy board URL`}
|
|
||||||
>
|
|
||||||
<HiLink className={`h-4 w-4 text-light-900 dark:text-dark-900`} />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CopyBoardLink = () => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleCopyBoardLink}
|
||||||
|
className="rounded p-1.5 transition-all hover:bg-light-200 focus:outline-none dark:hover:bg-dark-100"
|
||||||
|
aria-label="Copy board URL"
|
||||||
|
>
|
||||||
|
<HiLink className="h-4 w-4 text-light-900 dark:text-dark-900" />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
const pathWithoutQuery = router.asPath.split("?")[0];
|
const pathWithoutQuery = router.asPath.split("?")[0];
|
||||||
const splitPath = pathWithoutQuery?.split("/") ?? [];
|
const splitPath = pathWithoutQuery?.split("/") ?? [];
|
||||||
const cardPublicId = splitPath.length > 3 ? splitPath[3] : null;
|
const cardPublicId = splitPath.length > 3 ? splitPath[3] : null;
|
||||||
|
|||||||
@@ -58,28 +58,6 @@ export default function Avatar({
|
|||||||
const [crop, setCrop] = useState<PercentCrop>();
|
const [crop, setCrop] = useState<PercentCrop>();
|
||||||
const imgRef = useRef<HTMLImageElement | null>(null);
|
const imgRef = useRef<HTMLImageElement | null>(null);
|
||||||
|
|
||||||
const updateUser = api.user.update.useMutation({
|
|
||||||
onSuccess: async () => {
|
|
||||||
showPopup({
|
|
||||||
header: t`Profile image updated`,
|
|
||||||
message: t`Your profile image has been updated.`,
|
|
||||||
icon: "success",
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
await utils.user.getUser.refetch();
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
showPopup({
|
|
||||||
header: t`Error updating profile image`,
|
|
||||||
message: t`Please try again later, or contact customer support.`,
|
|
||||||
icon: "error",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const avatarUrl = userImage ? getAvatarUrl(userImage) : undefined;
|
const avatarUrl = userImage ? getAvatarUrl(userImage) : undefined;
|
||||||
|
|
||||||
@@ -187,29 +165,32 @@ export default function Avatar({
|
|||||||
const originalExt = selectedFile.name.split(".").pop() ?? "jpg";
|
const originalExt = selectedFile.name.split(".").pop() ?? "jpg";
|
||||||
const fileName = `${userId}/avatar-${generateUID()}.${originalExt}`;
|
const fileName = `${userId}/avatar-${generateUID()}.${originalExt}`;
|
||||||
|
|
||||||
|
const baseUrl = env("NEXT_PUBLIC_BASE_URL") ?? "";
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
env("NEXT_PUBLIC_BASE_URL") + "/api/upload/image",
|
`${baseUrl}/api/upload/avatar`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": blob.type,
|
||||||
|
"x-original-filename": fileName,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ filename: fileName, contentType: blob.type }),
|
body: blob,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to get pre-signed URL");
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to upload profile image");
|
||||||
|
}
|
||||||
|
|
||||||
const { url } = (await response.json()) as { url: string };
|
// User image is updated in the backend, refresh user data
|
||||||
|
await utils.user.getUser.refetch();
|
||||||
|
|
||||||
const uploadResponse = await fetch(url, {
|
showPopup({
|
||||||
method: "PUT",
|
header: t`Profile image updated`,
|
||||||
body: blob,
|
message: t`Your profile image has been updated.`,
|
||||||
|
icon: "success",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!uploadResponse.ok) throw new Error("Failed to upload profile image");
|
|
||||||
|
|
||||||
updateUser.mutate({ image: fileName });
|
|
||||||
setCropDialogOpen(false);
|
setCropDialogOpen(false);
|
||||||
resetCropState();
|
resetCropState();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -227,7 +208,7 @@ export default function Avatar({
|
|||||||
resetCropState,
|
resetCropState,
|
||||||
selectedFile,
|
selectedFile,
|
||||||
showPopup,
|
showPopup,
|
||||||
updateUser,
|
utils.user.getUser,
|
||||||
userId,
|
userId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,10 @@
|
|||||||
"./utils/rateLimit": {
|
"./utils/rateLimit": {
|
||||||
"types": "./dist/utils/rateLimit.d.ts",
|
"types": "./dist/utils/rateLimit.d.ts",
|
||||||
"default": "./src/utils/rateLimit.ts"
|
"default": "./src/utils/rateLimit.ts"
|
||||||
|
},
|
||||||
|
"./utils/permissions": {
|
||||||
|
"types": "./dist/utils/permissions.d.ts",
|
||||||
|
"default": "./src/utils/permissions.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
@@ -39,8 +43,6 @@
|
|||||||
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.802.0",
|
|
||||||
"@aws-sdk/s3-request-presigner": "^3.812.0",
|
|
||||||
"@kan/auth": "workspace:*",
|
"@kan/auth": "workspace:*",
|
||||||
"@kan/db": "workspace:*",
|
"@kan/db": "workspace:*",
|
||||||
"@kan/email": "workspace:^",
|
"@kan/email": "workspace:^",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { generateUID } from "@kan/shared/utils";
|
|||||||
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
import { assertPermission } from "../utils/permissions";
|
import { assertPermission } from "../utils/permissions";
|
||||||
import { deleteObject, generateUploadUrl } from "../utils/s3";
|
import { deleteObject, generateUploadUrl } from "@kan/shared/utils";
|
||||||
|
|
||||||
export const attachmentRouter = createTRPCRouter({
|
export const attachmentRouter = createTRPCRouter({
|
||||||
generateUploadUrl: protectedProcedure
|
generateUploadUrl: protectedProcedure
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import * as workspaceRepo from "@kan/db/repository/workspace.repo";
|
|||||||
import { colours } from "@kan/shared/constants";
|
import { colours } from "@kan/shared/constants";
|
||||||
import {
|
import {
|
||||||
convertDueDateFiltersToRanges,
|
convertDueDateFiltersToRanges,
|
||||||
|
generateAvatarUrl,
|
||||||
generateSlug,
|
generateSlug,
|
||||||
generateUID,
|
generateUID,
|
||||||
} from "@kan/shared/utils";
|
} from "@kan/shared/utils";
|
||||||
@@ -142,7 +143,40 @@ export const boardRouter = createTRPCRouter({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
if (!result) {
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Board with public ID ${input.boardPublicId} not found`,
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate presigned URLs for workspace member avatars
|
||||||
|
const workspaceWithAvatarUrls = result.workspace
|
||||||
|
? {
|
||||||
|
...result.workspace,
|
||||||
|
members: await Promise.all(
|
||||||
|
result.workspace.members.map(async (member) => {
|
||||||
|
if (!member.user?.image) {
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarUrl = await generateAvatarUrl(member.user.image);
|
||||||
|
return {
|
||||||
|
...member,
|
||||||
|
user: {
|
||||||
|
...member.user,
|
||||||
|
image: avatarUrl,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: result.workspace;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
workspace: workspaceWithAvatarUrls,
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
bySlug: publicProcedure
|
bySlug: publicProcedure
|
||||||
.meta({
|
.meta({
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import * as workspaceRepo from "@kan/db/repository/workspace.repo";
|
|||||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
||||||
import { mergeActivities } from "../utils/activities";
|
import { mergeActivities } from "../utils/activities";
|
||||||
import { assertCanDelete, assertCanEdit, assertPermission } from "../utils/permissions";
|
import { assertCanDelete, assertCanEdit, assertPermission } from "../utils/permissions";
|
||||||
import { generateDownloadUrl } from "../utils/s3";
|
import { generateAttachmentUrl, generateAvatarUrl } from "@kan/shared/utils";
|
||||||
|
|
||||||
export const cardRouter = createTRPCRouter({
|
export const cardRouter = createTRPCRouter({
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
@@ -631,45 +631,54 @@ export const cardRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Generate URLs for all attachments
|
// Generate URLs for all attachments
|
||||||
const bucket = process.env.NEXT_PUBLIC_ATTACHMENTS_BUCKET_NAME;
|
const attachmentsWithUrls = await Promise.all(
|
||||||
if (result.attachments && Array.isArray(result.attachments)) {
|
result.attachments.map(async (attachment) => {
|
||||||
const attachments = result.attachments as {
|
const url = await generateAttachmentUrl(attachment.s3Key);
|
||||||
publicId: string;
|
return {
|
||||||
contentType: string;
|
publicId: attachment.publicId,
|
||||||
s3Key: string;
|
contentType: attachment.contentType,
|
||||||
originalFilename: string | null;
|
s3Key: attachment.s3Key,
|
||||||
size?: number | null;
|
originalFilename: attachment.originalFilename,
|
||||||
}[];
|
size: attachment.size,
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const attachmentsWithUrls = await Promise.all(
|
// Generate presigned URLs for workspace member avatars
|
||||||
attachments.map(async (attachment) => {
|
const workspaceWithAvatarUrls = result.list.board.workspace
|
||||||
const base = {
|
? {
|
||||||
publicId: attachment.publicId,
|
...result.list.board.workspace,
|
||||||
contentType: attachment.contentType,
|
members: await Promise.all(
|
||||||
s3Key: attachment.s3Key,
|
result.list.board.workspace.members.map(async (member) => {
|
||||||
originalFilename: attachment.originalFilename,
|
if (!member.user?.image) {
|
||||||
size: attachment.size,
|
return member;
|
||||||
};
|
}
|
||||||
if (!bucket || !attachment.s3Key) {
|
|
||||||
return { ...base, url: null };
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const url = await generateDownloadUrl(
|
|
||||||
bucket,
|
|
||||||
attachment.s3Key,
|
|
||||||
86400, // 24 hours expiration
|
|
||||||
);
|
|
||||||
return { ...base, url };
|
|
||||||
} catch {
|
|
||||||
// If URL generation fails, return attachment with url: null
|
|
||||||
return { ...base, url: null };
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
return { ...result, attachments: attachmentsWithUrls };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...result, attachments: [] };
|
const avatarUrl = await generateAvatarUrl(member.user.image);
|
||||||
|
return {
|
||||||
|
...member,
|
||||||
|
user: {
|
||||||
|
...member.user,
|
||||||
|
image: avatarUrl,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: result.list.board.workspace;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
attachments: attachmentsWithUrls,
|
||||||
|
list: {
|
||||||
|
...result.list,
|
||||||
|
board: {
|
||||||
|
...result.list.board,
|
||||||
|
workspace: workspaceWithAvatarUrls,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
getActivities: publicProcedure
|
getActivities: publicProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@@ -738,7 +747,39 @@ export const cardRouter = createTRPCRouter({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const mergedActivities = mergeActivities(result.activities);
|
// Generate presigned URLs for user avatars in activities
|
||||||
|
const activitiesWithAvatarUrls = await Promise.all(
|
||||||
|
result.activities.map(async (activity) => {
|
||||||
|
const updatedActivity = { ...activity };
|
||||||
|
|
||||||
|
// Generate presigned URL for activity user avatar
|
||||||
|
if (activity.user?.image) {
|
||||||
|
const userAvatarUrl = await generateAvatarUrl(activity.user.image);
|
||||||
|
updatedActivity.user = {
|
||||||
|
...activity.user,
|
||||||
|
image: userAvatarUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate presigned URL for member user avatar (if exists)
|
||||||
|
if (activity.member?.user?.image) {
|
||||||
|
const memberAvatarUrl = await generateAvatarUrl(
|
||||||
|
activity.member.user.image,
|
||||||
|
);
|
||||||
|
updatedActivity.member = {
|
||||||
|
...activity.member,
|
||||||
|
user: {
|
||||||
|
...activity.member.user,
|
||||||
|
image: memberAvatarUrl,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedActivity;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const mergedActivities = mergeActivities(activitiesWithAvatarUrls);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activities: mergedActivities,
|
activities: mergedActivities,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
createTRPCRouter,
|
createTRPCRouter,
|
||||||
publicProcedure,
|
publicProcedure,
|
||||||
} from "../trpc";
|
} from "../trpc";
|
||||||
import { createS3Client } from "../utils/s3";
|
import { createS3Client } from "@kan/shared/utils";
|
||||||
|
|
||||||
const checkDatabaseConnection = async (db: dbClient) => {
|
const checkDatabaseConnection = async (db: dbClient) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { z } from "zod";
|
|||||||
import * as userRepo from "@kan/db/repository/user.repo";
|
import * as userRepo from "@kan/db/repository/user.repo";
|
||||||
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
|
import { generateAvatarUrl } from "@kan/shared/utils";
|
||||||
|
|
||||||
export const userRouter = createTRPCRouter({
|
export const userRouter = createTRPCRouter({
|
||||||
getUser: protectedProcedure
|
getUser: protectedProcedure
|
||||||
@@ -55,8 +56,12 @@ export const userRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const apiKey = result.apiKeys[0];
|
const apiKey = result.apiKeys[0];
|
||||||
|
|
||||||
|
// Generate presigned URL for avatar
|
||||||
|
const imageUrl = await generateAvatarUrl(result.image);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
|
image: imageUrl,
|
||||||
apiKey: apiKey ?? null,
|
apiKey: apiKey ?? null,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@@ -102,6 +107,12 @@ export const userRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
// Generate presigned URL for avatar
|
||||||
|
const imageUrl = await generateAvatarUrl(result.image);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
image: imageUrl,
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { generateUID } from "@kan/shared/utils";
|
|||||||
|
|
||||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
||||||
import { assertPermission } from "../utils/permissions";
|
import { assertPermission } from "../utils/permissions";
|
||||||
|
import { generateAvatarUrl } from "@kan/shared/utils";
|
||||||
|
|
||||||
export const workspaceRouter = createTRPCRouter({
|
export const workspaceRouter = createTRPCRouter({
|
||||||
all: protectedProcedure
|
all: protectedProcedure
|
||||||
@@ -86,9 +87,27 @@ export const workspaceRouter = createTRPCRouter({
|
|||||||
const shouldShowEmails =
|
const shouldShowEmails =
|
||||||
isAdmin || result.showEmailsToMembers === true;
|
isAdmin || result.showEmailsToMembers === true;
|
||||||
|
|
||||||
|
// Generate presigned URLs for member avatars
|
||||||
|
const membersWithAvatarUrls = await Promise.all(
|
||||||
|
result.members.map(async (member) => {
|
||||||
|
if (!member.user?.image) {
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarUrl = await generateAvatarUrl(member.user.image);
|
||||||
|
return {
|
||||||
|
...member,
|
||||||
|
user: {
|
||||||
|
...member.user,
|
||||||
|
image: avatarUrl,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// If emails should be hidden, filter them out
|
// If emails should be hidden, filter them out
|
||||||
if (!shouldShowEmails) {
|
if (!shouldShowEmails) {
|
||||||
const sanitizedMembers = result.members.map((member) => {
|
const sanitizedMembers = membersWithAvatarUrls.map((member) => {
|
||||||
// If user doesn't have a display name, use anonymous identifier
|
// If user doesn't have a display name, use anonymous identifier
|
||||||
const displayName =
|
const displayName =
|
||||||
member.user?.name?.trim() ?? `anonymous_${member.publicId}`;
|
member.user?.name?.trim() ?? `anonymous_${member.publicId}`;
|
||||||
@@ -120,7 +139,10 @@ export const workspaceRouter = createTRPCRouter({
|
|||||||
} as Awaited<ReturnType<typeof workspaceRepo.getByPublicIdWithMembers>>;
|
} as Awaited<ReturnType<typeof workspaceRepo.getByPublicIdWithMembers>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return {
|
||||||
|
...result,
|
||||||
|
members: membersWithAvatarUrls,
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
bySlug: publicProcedure
|
bySlug: publicProcedure
|
||||||
.meta({
|
.meta({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
import { PutObjectCommand } from "@aws-sdk/client-s3";
|
||||||
import { ChatOrPushProviderEnum } from "@novu/api/models/components";
|
import { ChatOrPushProviderEnum } from "@novu/api/models/components";
|
||||||
import { createAuthMiddleware } from "better-auth/api";
|
import { createAuthMiddleware } from "better-auth/api";
|
||||||
import { env } from "next-runtime-env";
|
import { env } from "next-runtime-env";
|
||||||
@@ -7,7 +7,7 @@ import type { dbClient } from "@kan/db/client";
|
|||||||
import * as memberRepo from "@kan/db/repository/member.repo";
|
import * as memberRepo from "@kan/db/repository/member.repo";
|
||||||
import * as userRepo from "@kan/db/repository/user.repo";
|
import * as userRepo from "@kan/db/repository/user.repo";
|
||||||
import { notificationClient } from "@kan/email";
|
import { notificationClient } from "@kan/email";
|
||||||
import { createEmailUnsubscribeLink } from "@kan/shared";
|
import { createEmailUnsubscribeLink, createS3Client } from "@kan/shared";
|
||||||
|
|
||||||
import { downloadImage } from "./utils";
|
import { downloadImage } from "./utils";
|
||||||
|
|
||||||
@@ -61,20 +61,7 @@ export function createDatabaseHooks(db: dbClient) {
|
|||||||
!user.image.includes(storageDomain)
|
!user.image.includes(storageDomain)
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const credentials =
|
const client = createS3Client();
|
||||||
env("S3_ACCESS_KEY_ID") && env("S3_SECRET_ACCESS_KEY")
|
|
||||||
? {
|
|
||||||
accessKeyId: env("S3_ACCESS_KEY_ID")!,
|
|
||||||
secretAccessKey: env("S3_SECRET_ACCESS_KEY")!,
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const client = new S3Client({
|
|
||||||
region: env("S3_REGION") ?? "",
|
|
||||||
endpoint: env("S3_ENDPOINT") ?? "",
|
|
||||||
forcePathStyle: env("S3_FORCE_PATH_STYLE") === "true",
|
|
||||||
credentials,
|
|
||||||
});
|
|
||||||
|
|
||||||
const allowedFileExtensions = ["jpg", "jpeg", "png", "webp"];
|
const allowedFileExtensions = ["jpg", "jpeg", "png", "webp"];
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,8 @@
|
|||||||
},
|
},
|
||||||
"prettier": "@kan/prettier-config",
|
"prettier": "@kan/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.802.0",
|
||||||
|
"@aws-sdk/s3-request-presigner": "^3.812.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"jose": "^6.1.2",
|
"jose": "^6.1.2",
|
||||||
"nanoid": "^5.0.9",
|
"nanoid": "^5.0.9",
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ export * from "./generateSlug";
|
|||||||
export * from "./subscriptions";
|
export * from "./subscriptions";
|
||||||
export * from "./email";
|
export * from "./email";
|
||||||
export * from "./dueDateFilters";
|
export * from "./dueDateFilters";
|
||||||
|
export * from "./s3";
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
S3Client,
|
S3Client,
|
||||||
} from "@aws-sdk/client-s3";
|
} from "@aws-sdk/client-s3";
|
||||||
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||||
|
import { env } from "next-runtime-env";
|
||||||
|
|
||||||
export function createS3Client() {
|
export function createS3Client() {
|
||||||
const credentials =
|
const credentials =
|
||||||
@@ -67,3 +68,60 @@ export async function deleteObject(bucket: string, key: string) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate presigned URL for an avatar image
|
||||||
|
* Returns the URL as-is if it's already a full URL (external provider)
|
||||||
|
* Returns presigned URL if it's an S3 key
|
||||||
|
* Returns null if image key is missing, bucket is not configured, or URL generation fails
|
||||||
|
*/
|
||||||
|
export async function generateAvatarUrl(
|
||||||
|
imageKey: string | null | undefined,
|
||||||
|
expiresIn = 86400, // 24 hours
|
||||||
|
): Promise<string | null> {
|
||||||
|
if (!imageKey) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageKey.startsWith("http://") || imageKey.startsWith("https://")) {
|
||||||
|
return imageKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bucket = env("NEXT_PUBLIC_AVATAR_BUCKET_NAME");
|
||||||
|
if (!bucket) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await generateDownloadUrl(bucket, imageKey, expiresIn);
|
||||||
|
} catch {
|
||||||
|
// If URL generation fails, return null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate presigned URL for an attachment
|
||||||
|
* Returns null if attachment key is missing, bucket is not configured, or URL generation fails
|
||||||
|
*/
|
||||||
|
export async function generateAttachmentUrl(
|
||||||
|
attachmentKey: string | null | undefined,
|
||||||
|
expiresIn = 86400, // 24 hours
|
||||||
|
): Promise<string | null> {
|
||||||
|
if (!attachmentKey) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bucket = env("NEXT_PUBLIC_ATTACHMENTS_BUCKET_NAME");
|
||||||
|
if (!bucket) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await generateDownloadUrl(bucket, attachmentKey, expiresIn);
|
||||||
|
} catch {
|
||||||
|
// If URL generation fails, return null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
326
pnpm-lock.yaml
generated
326
pnpm-lock.yaml
generated
@@ -82,6 +82,9 @@ importers:
|
|||||||
'@aws-sdk/client-s3':
|
'@aws-sdk/client-s3':
|
||||||
specifier: ^3.802.0
|
specifier: ^3.802.0
|
||||||
version: 3.879.0
|
version: 3.879.0
|
||||||
|
'@aws-sdk/lib-storage':
|
||||||
|
specifier: ^3.802.0
|
||||||
|
version: 3.985.0(@aws-sdk/client-s3@3.879.0)
|
||||||
'@aws-sdk/s3-request-presigner':
|
'@aws-sdk/s3-request-presigner':
|
||||||
specifier: ^3.812.0
|
specifier: ^3.812.0
|
||||||
version: 3.879.0
|
version: 3.879.0
|
||||||
@@ -136,6 +139,9 @@ importers:
|
|||||||
'@tiptap/extension-placeholder':
|
'@tiptap/extension-placeholder':
|
||||||
specifier: ^2.14.0
|
specifier: ^2.14.0
|
||||||
version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)
|
version: 2.26.1(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))(@tiptap/pm@2.26.1)
|
||||||
|
'@tiptap/extension-typography':
|
||||||
|
specifier: ^3.18.0
|
||||||
|
version: 3.18.0(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))
|
||||||
'@tiptap/pm':
|
'@tiptap/pm':
|
||||||
specifier: ^2.14.0
|
specifier: ^2.14.0
|
||||||
version: 2.26.1
|
version: 2.26.1
|
||||||
@@ -293,12 +299,9 @@ importers:
|
|||||||
|
|
||||||
packages/api:
|
packages/api:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/client-s3':
|
'@aws-sdk/lib-storage':
|
||||||
specifier: ^3.802.0
|
specifier: ^3.802.0
|
||||||
version: 3.879.0
|
version: 3.985.0(@aws-sdk/client-s3@3.879.0)
|
||||||
'@aws-sdk/s3-request-presigner':
|
|
||||||
specifier: ^3.812.0
|
|
||||||
version: 3.879.0
|
|
||||||
'@kan/auth':
|
'@kan/auth':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../auth
|
version: link:../auth
|
||||||
@@ -483,6 +486,12 @@ importers:
|
|||||||
|
|
||||||
packages/shared:
|
packages/shared:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@aws-sdk/client-s3':
|
||||||
|
specifier: ^3.802.0
|
||||||
|
version: 3.879.0
|
||||||
|
'@aws-sdk/s3-request-presigner':
|
||||||
|
specifier: ^3.812.0
|
||||||
|
version: 3.879.0
|
||||||
date-fns:
|
date-fns:
|
||||||
specifier: ^4.1.0
|
specifier: ^4.1.0
|
||||||
version: 4.1.0
|
version: 4.1.0
|
||||||
@@ -734,6 +743,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-Jy4uPFfGzHk1Mxy+/Wr43vuw9yXsE2yiF4e4598vc3aJfO0YtA2nSfbKD3PNKRORwXbeKqWPfph9SCKQpWoxEg==}
|
resolution: {integrity: sha512-Jy4uPFfGzHk1Mxy+/Wr43vuw9yXsE2yiF4e4598vc3aJfO0YtA2nSfbKD3PNKRORwXbeKqWPfph9SCKQpWoxEg==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@aws-sdk/lib-storage@3.985.0':
|
||||||
|
resolution: {integrity: sha512-EnqXf2E+5+7e5zuz8mPl+3Dk/DI0ObSL8s7Tsf6H8FL/p2BJw2LhgtxPnFZUmpanpoMO0hC9/qjXNd+aGPV0/w==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@aws-sdk/client-s3': ^3.985.0
|
||||||
|
|
||||||
'@aws-sdk/middleware-bucket-endpoint@3.873.0':
|
'@aws-sdk/middleware-bucket-endpoint@3.873.0':
|
||||||
resolution: {integrity: sha512-b4bvr0QdADeTUs+lPc9Z48kXzbKHXQKgTvxx/jXDgSW9tv4KmYPO1gIj6Z9dcrBkRWQuUtSW3Tu2S5n6pe+zeg==}
|
resolution: {integrity: sha512-b4bvr0QdADeTUs+lPc9Z48kXzbKHXQKgTvxx/jXDgSW9tv4KmYPO1gIj6Z9dcrBkRWQuUtSW3Tu2S5n6pe+zeg==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -3300,6 +3315,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-jcrqdTQurIrBbUm4W2YdLVMQDoL0sA9DTxYd2s+R/y+2U9NLOP7Xf/YqfSg1FZhlZIYEnvk2mwbyvIfdLEPo8g==}
|
resolution: {integrity: sha512-jcrqdTQurIrBbUm4W2YdLVMQDoL0sA9DTxYd2s+R/y+2U9NLOP7Xf/YqfSg1FZhlZIYEnvk2mwbyvIfdLEPo8g==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/abort-controller@4.2.8':
|
||||||
|
resolution: {integrity: sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/chunked-blob-reader-native@4.0.0':
|
'@smithy/chunked-blob-reader-native@4.0.0':
|
||||||
resolution: {integrity: sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==}
|
resolution: {integrity: sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -3312,6 +3331,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-viuHMxBAqydkB0AfWwHIdwf/PRH2z5KHGUzqyRtS/Wv+n3IHI993Sk76VCA7dD/+GzgGOmlJDITfPcJC1nIVIw==}
|
resolution: {integrity: sha512-viuHMxBAqydkB0AfWwHIdwf/PRH2z5KHGUzqyRtS/Wv+n3IHI993Sk76VCA7dD/+GzgGOmlJDITfPcJC1nIVIw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/core@3.22.1':
|
||||||
|
resolution: {integrity: sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/core@3.9.0':
|
'@smithy/core@3.9.0':
|
||||||
resolution: {integrity: sha512-B/GknvCfS3llXd/b++hcrwIuqnEozQDnRL4sBmOac5/z/dr0/yG1PURNPOyU4Lsiy1IyTj8scPxVqRs5dYWf6A==}
|
resolution: {integrity: sha512-B/GknvCfS3llXd/b++hcrwIuqnEozQDnRL4sBmOac5/z/dr0/yG1PURNPOyU4Lsiy1IyTj8scPxVqRs5dYWf6A==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -3344,6 +3367,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-61WjM0PWmZJR+SnmzaKI7t7G0UkkNFboDpzIdzSoy7TByUzlxo18Qlh9s71qug4AY4hlH/CwXdubMtkcNEb/sQ==}
|
resolution: {integrity: sha512-61WjM0PWmZJR+SnmzaKI7t7G0UkkNFboDpzIdzSoy7TByUzlxo18Qlh9s71qug4AY4hlH/CwXdubMtkcNEb/sQ==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/fetch-http-handler@5.3.9':
|
||||||
|
resolution: {integrity: sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/hash-blob-browser@4.0.5':
|
'@smithy/hash-blob-browser@4.0.5':
|
||||||
resolution: {integrity: sha512-F7MmCd3FH/Q2edhcKd+qulWkwfChHbc9nhguBlVjSUE6hVHhec3q6uPQ+0u69S6ppvLtR3eStfCuEKMXBXhvvA==}
|
resolution: {integrity: sha512-F7MmCd3FH/Q2edhcKd+qulWkwfChHbc9nhguBlVjSUE6hVHhec3q6uPQ+0u69S6ppvLtR3eStfCuEKMXBXhvvA==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -3368,6 +3395,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==}
|
resolution: {integrity: sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/is-array-buffer@4.2.0':
|
||||||
|
resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/md5-js@4.0.5':
|
'@smithy/md5-js@4.0.5':
|
||||||
resolution: {integrity: sha512-8n2XCwdUbGr8W/XhMTaxILkVlw2QebkVTn5tm3HOcbPbOpWg89zr6dPXsH8xbeTsbTXlJvlJNTQsKAIoqQGbdA==}
|
resolution: {integrity: sha512-8n2XCwdUbGr8W/XhMTaxILkVlw2QebkVTn5tm3HOcbPbOpWg89zr6dPXsH8xbeTsbTXlJvlJNTQsKAIoqQGbdA==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -3380,6 +3411,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-EAlEPncqo03siNZJ9Tm6adKCQ+sw5fNU8ncxWwaH0zTCwMPsgmERTi6CEKaermZdgJb+4Yvh0NFm36HeO4PGgQ==}
|
resolution: {integrity: sha512-EAlEPncqo03siNZJ9Tm6adKCQ+sw5fNU8ncxWwaH0zTCwMPsgmERTi6CEKaermZdgJb+4Yvh0NFm36HeO4PGgQ==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/middleware-endpoint@4.4.13':
|
||||||
|
resolution: {integrity: sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/middleware-retry@4.1.20':
|
'@smithy/middleware-retry@4.1.20':
|
||||||
resolution: {integrity: sha512-T3maNEm3Masae99eFdx1Q7PIqBBEVOvRd5hralqKZNeIivnoGNx5OFtI3DiZ5gCjUkl0mNondlzSXeVxkinh7Q==}
|
resolution: {integrity: sha512-T3maNEm3Masae99eFdx1Q7PIqBBEVOvRd5hralqKZNeIivnoGNx5OFtI3DiZ5gCjUkl0mNondlzSXeVxkinh7Q==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -3388,34 +3423,66 @@ packages:
|
|||||||
resolution: {integrity: sha512-uAFFR4dpeoJPGz8x9mhxp+RPjo5wW0QEEIPPPbLXiRRWeCATf/Km3gKIVR5vaP8bN1kgsPhcEeh+IZvUlBv6Xg==}
|
resolution: {integrity: sha512-uAFFR4dpeoJPGz8x9mhxp+RPjo5wW0QEEIPPPbLXiRRWeCATf/Km3gKIVR5vaP8bN1kgsPhcEeh+IZvUlBv6Xg==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/middleware-serde@4.2.9':
|
||||||
|
resolution: {integrity: sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/middleware-stack@4.0.5':
|
'@smithy/middleware-stack@4.0.5':
|
||||||
resolution: {integrity: sha512-/yoHDXZPh3ocRVyeWQFvC44u8seu3eYzZRveCMfgMOBcNKnAmOvjbL9+Cp5XKSIi9iYA9PECUuW2teDAk8T+OQ==}
|
resolution: {integrity: sha512-/yoHDXZPh3ocRVyeWQFvC44u8seu3eYzZRveCMfgMOBcNKnAmOvjbL9+Cp5XKSIi9iYA9PECUuW2teDAk8T+OQ==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/middleware-stack@4.2.8':
|
||||||
|
resolution: {integrity: sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/node-config-provider@4.1.4':
|
'@smithy/node-config-provider@4.1.4':
|
||||||
resolution: {integrity: sha512-+UDQV/k42jLEPPHSn39l0Bmc4sB1xtdI9Gd47fzo/0PbXzJ7ylgaOByVjF5EeQIumkepnrJyfx86dPa9p47Y+w==}
|
resolution: {integrity: sha512-+UDQV/k42jLEPPHSn39l0Bmc4sB1xtdI9Gd47fzo/0PbXzJ7ylgaOByVjF5EeQIumkepnrJyfx86dPa9p47Y+w==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/node-config-provider@4.3.8':
|
||||||
|
resolution: {integrity: sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/node-http-handler@4.1.1':
|
'@smithy/node-http-handler@4.1.1':
|
||||||
resolution: {integrity: sha512-RHnlHqFpoVdjSPPiYy/t40Zovf3BBHc2oemgD7VsVTFFZrU5erFFe0n52OANZZ/5sbshgD93sOh5r6I35Xmpaw==}
|
resolution: {integrity: sha512-RHnlHqFpoVdjSPPiYy/t40Zovf3BBHc2oemgD7VsVTFFZrU5erFFe0n52OANZZ/5sbshgD93sOh5r6I35Xmpaw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/node-http-handler@4.4.9':
|
||||||
|
resolution: {integrity: sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/property-provider@4.0.5':
|
'@smithy/property-provider@4.0.5':
|
||||||
resolution: {integrity: sha512-R/bswf59T/n9ZgfgUICAZoWYKBHcsVDurAGX88zsiUtOTA/xUAPyiT+qkNCPwFn43pZqN84M4MiUsbSGQmgFIQ==}
|
resolution: {integrity: sha512-R/bswf59T/n9ZgfgUICAZoWYKBHcsVDurAGX88zsiUtOTA/xUAPyiT+qkNCPwFn43pZqN84M4MiUsbSGQmgFIQ==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/property-provider@4.2.8':
|
||||||
|
resolution: {integrity: sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/protocol-http@5.1.3':
|
'@smithy/protocol-http@5.1.3':
|
||||||
resolution: {integrity: sha512-fCJd2ZR7D22XhDY0l+92pUag/7je2BztPRQ01gU5bMChcyI0rlly7QFibnYHzcxDvccMjlpM/Q1ev8ceRIb48w==}
|
resolution: {integrity: sha512-fCJd2ZR7D22XhDY0l+92pUag/7je2BztPRQ01gU5bMChcyI0rlly7QFibnYHzcxDvccMjlpM/Q1ev8ceRIb48w==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/protocol-http@5.3.8':
|
||||||
|
resolution: {integrity: sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/querystring-builder@4.0.5':
|
'@smithy/querystring-builder@4.0.5':
|
||||||
resolution: {integrity: sha512-NJeSCU57piZ56c+/wY+AbAw6rxCCAOZLCIniRE7wqvndqxcKKDOXzwWjrY7wGKEISfhL9gBbAaWWgHsUGedk+A==}
|
resolution: {integrity: sha512-NJeSCU57piZ56c+/wY+AbAw6rxCCAOZLCIniRE7wqvndqxcKKDOXzwWjrY7wGKEISfhL9gBbAaWWgHsUGedk+A==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/querystring-builder@4.2.8':
|
||||||
|
resolution: {integrity: sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/querystring-parser@4.0.5':
|
'@smithy/querystring-parser@4.0.5':
|
||||||
resolution: {integrity: sha512-6SV7md2CzNG/WUeTjVe6Dj8noH32r4MnUeFKZrnVYsQxpGSIcphAanQMayi8jJLZAWm6pdM9ZXvKCpWOsIGg0w==}
|
resolution: {integrity: sha512-6SV7md2CzNG/WUeTjVe6Dj8noH32r4MnUeFKZrnVYsQxpGSIcphAanQMayi8jJLZAWm6pdM9ZXvKCpWOsIGg0w==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/querystring-parser@4.2.8':
|
||||||
|
resolution: {integrity: sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/service-error-classification@4.0.7':
|
'@smithy/service-error-classification@4.0.7':
|
||||||
resolution: {integrity: sha512-XvRHOipqpwNhEjDf2L5gJowZEm5nsxC16pAZOeEcsygdjv9A2jdOh3YoDQvOXBGTsaJk6mNWtzWalOB9976Wlg==}
|
resolution: {integrity: sha512-XvRHOipqpwNhEjDf2L5gJowZEm5nsxC16pAZOeEcsygdjv9A2jdOh3YoDQvOXBGTsaJk6mNWtzWalOB9976Wlg==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -3424,14 +3491,26 @@ packages:
|
|||||||
resolution: {integrity: sha512-YVVwehRDuehgoXdEL4r1tAAzdaDgaC9EQvhK0lEbfnbrd0bd5+CTQumbdPryX3J2shT7ZqQE+jPW4lmNBAB8JQ==}
|
resolution: {integrity: sha512-YVVwehRDuehgoXdEL4r1tAAzdaDgaC9EQvhK0lEbfnbrd0bd5+CTQumbdPryX3J2shT7ZqQE+jPW4lmNBAB8JQ==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/shared-ini-file-loader@4.4.3':
|
||||||
|
resolution: {integrity: sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/signature-v4@5.1.3':
|
'@smithy/signature-v4@5.1.3':
|
||||||
resolution: {integrity: sha512-mARDSXSEgllNzMw6N+mC+r1AQlEBO3meEAkR/UlfAgnMzJUB3goRBWgip1EAMG99wh36MDqzo86SfIX5Y+VEaw==}
|
resolution: {integrity: sha512-mARDSXSEgllNzMw6N+mC+r1AQlEBO3meEAkR/UlfAgnMzJUB3goRBWgip1EAMG99wh36MDqzo86SfIX5Y+VEaw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/smithy-client@4.11.2':
|
||||||
|
resolution: {integrity: sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/smithy-client@4.5.0':
|
'@smithy/smithy-client@4.5.0':
|
||||||
resolution: {integrity: sha512-ZSdE3vl0MuVbEwJBxSftm0J5nL/gw76xp5WF13zW9cN18MFuFXD5/LV0QD8P+sCU5bSWGyy6CTgUupE1HhOo1A==}
|
resolution: {integrity: sha512-ZSdE3vl0MuVbEwJBxSftm0J5nL/gw76xp5WF13zW9cN18MFuFXD5/LV0QD8P+sCU5bSWGyy6CTgUupE1HhOo1A==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/types@4.12.0':
|
||||||
|
resolution: {integrity: sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/types@4.3.2':
|
'@smithy/types@4.3.2':
|
||||||
resolution: {integrity: sha512-QO4zghLxiQ5W9UZmX2Lo0nta2PuE1sSrXUYDoaB6HMR762C0P7v/HEPHf6ZdglTVssJG1bsrSBxdc3quvDSihw==}
|
resolution: {integrity: sha512-QO4zghLxiQ5W9UZmX2Lo0nta2PuE1sSrXUYDoaB6HMR762C0P7v/HEPHf6ZdglTVssJG1bsrSBxdc3quvDSihw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -3440,14 +3519,26 @@ packages:
|
|||||||
resolution: {integrity: sha512-j+733Um7f1/DXjYhCbvNXABV53NyCRRA54C7bNEIxNPs0YjfRxeMKjjgm2jvTYrciZyCjsicHwQ6Q0ylo+NAUw==}
|
resolution: {integrity: sha512-j+733Um7f1/DXjYhCbvNXABV53NyCRRA54C7bNEIxNPs0YjfRxeMKjjgm2jvTYrciZyCjsicHwQ6Q0ylo+NAUw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/url-parser@4.2.8':
|
||||||
|
resolution: {integrity: sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/util-base64@4.0.0':
|
'@smithy/util-base64@4.0.0':
|
||||||
resolution: {integrity: sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==}
|
resolution: {integrity: sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/util-base64@4.3.0':
|
||||||
|
resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/util-body-length-browser@4.0.0':
|
'@smithy/util-body-length-browser@4.0.0':
|
||||||
resolution: {integrity: sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==}
|
resolution: {integrity: sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/util-body-length-browser@4.2.0':
|
||||||
|
resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/util-body-length-node@4.0.0':
|
'@smithy/util-body-length-node@4.0.0':
|
||||||
resolution: {integrity: sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==}
|
resolution: {integrity: sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -3460,6 +3551,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==}
|
resolution: {integrity: sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/util-buffer-from@4.2.0':
|
||||||
|
resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/util-config-provider@4.0.0':
|
'@smithy/util-config-provider@4.0.0':
|
||||||
resolution: {integrity: sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==}
|
resolution: {integrity: sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -3480,10 +3575,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==}
|
resolution: {integrity: sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/util-hex-encoding@4.2.0':
|
||||||
|
resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/util-middleware@4.0.5':
|
'@smithy/util-middleware@4.0.5':
|
||||||
resolution: {integrity: sha512-N40PfqsZHRSsByGB81HhSo+uvMxEHT+9e255S53pfBw/wI6WKDI7Jw9oyu5tJTLwZzV5DsMha3ji8jk9dsHmQQ==}
|
resolution: {integrity: sha512-N40PfqsZHRSsByGB81HhSo+uvMxEHT+9e255S53pfBw/wI6WKDI7Jw9oyu5tJTLwZzV5DsMha3ji8jk9dsHmQQ==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/util-middleware@4.2.8':
|
||||||
|
resolution: {integrity: sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/util-retry@4.0.7':
|
'@smithy/util-retry@4.0.7':
|
||||||
resolution: {integrity: sha512-TTO6rt0ppK70alZpkjwy+3nQlTiqNfoXja+qwuAchIEAIoSZW8Qyd76dvBv3I5bCpE38APafG23Y/u270NspiQ==}
|
resolution: {integrity: sha512-TTO6rt0ppK70alZpkjwy+3nQlTiqNfoXja+qwuAchIEAIoSZW8Qyd76dvBv3I5bCpE38APafG23Y/u270NspiQ==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -3492,10 +3595,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-vSKnvNZX2BXzl0U2RgCLOwWaAP9x/ddd/XobPK02pCbzRm5s55M53uwb1rl/Ts7RXZvdJZerPkA+en2FDghLuQ==}
|
resolution: {integrity: sha512-vSKnvNZX2BXzl0U2RgCLOwWaAP9x/ddd/XobPK02pCbzRm5s55M53uwb1rl/Ts7RXZvdJZerPkA+en2FDghLuQ==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/util-stream@4.5.11':
|
||||||
|
resolution: {integrity: sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/util-uri-escape@4.0.0':
|
'@smithy/util-uri-escape@4.0.0':
|
||||||
resolution: {integrity: sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==}
|
resolution: {integrity: sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/util-uri-escape@4.2.0':
|
||||||
|
resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/util-utf8@2.3.0':
|
'@smithy/util-utf8@2.3.0':
|
||||||
resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==}
|
resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
@@ -3504,10 +3615,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==}
|
resolution: {integrity: sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/util-utf8@4.2.0':
|
||||||
|
resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@smithy/util-waiter@4.0.7':
|
'@smithy/util-waiter@4.0.7':
|
||||||
resolution: {integrity: sha512-mYqtQXPmrwvUljaHyGxYUIIRI3qjBTEb/f5QFi3A6VlxhpmZd5mWXn9W+qUkf2pVE1Hv3SqxefiZOPGdxmO64A==}
|
resolution: {integrity: sha512-mYqtQXPmrwvUljaHyGxYUIIRI3qjBTEb/f5QFi3A6VlxhpmZd5mWXn9W+qUkf2pVE1Hv3SqxefiZOPGdxmO64A==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
|
'@smithy/uuid@1.1.0':
|
||||||
|
resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@socket.io/component-emitter@3.1.2':
|
'@socket.io/component-emitter@3.1.2':
|
||||||
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
|
||||||
|
|
||||||
@@ -3779,6 +3898,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tiptap/core': ^2.7.0
|
'@tiptap/core': ^2.7.0
|
||||||
|
|
||||||
|
'@tiptap/extension-typography@3.18.0':
|
||||||
|
resolution: {integrity: sha512-zTNGJjhJG3lObUhhTbDC1IOyi1DCiCd6i11xsnJDPy5BODYc7t7ZP6VMOWU0LIuMsK9kX02dXoNU7OarJgLpCg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@tiptap/core': ^3.18.0
|
||||||
|
|
||||||
'@tiptap/pm@2.26.1':
|
'@tiptap/pm@2.26.1':
|
||||||
resolution: {integrity: sha512-8aF+mY/vSHbGFqyG663ds84b+vca5Lge3tHdTMTKazxCnhXR9dn2oQJMnZ78YZvdRbkPkMJJHti9h3K7u2UQvw==}
|
resolution: {integrity: sha512-8aF+mY/vSHbGFqyG663ds84b+vca5Lge3tHdTMTKazxCnhXR9dn2oQJMnZ78YZvdRbkPkMJJHti9h3K7u2UQvw==}
|
||||||
|
|
||||||
@@ -4478,6 +4602,9 @@ packages:
|
|||||||
buffer-from@1.1.2:
|
buffer-from@1.1.2:
|
||||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||||
|
|
||||||
|
buffer@5.6.0:
|
||||||
|
resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==}
|
||||||
|
|
||||||
buffer@5.7.1:
|
buffer@5.7.1:
|
||||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||||
|
|
||||||
@@ -7752,6 +7879,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
stream-browserify@3.0.0:
|
||||||
|
resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==}
|
||||||
|
|
||||||
string-width@4.2.3:
|
string-width@4.2.3:
|
||||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -8894,6 +9024,17 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
|
'@aws-sdk/lib-storage@3.985.0(@aws-sdk/client-s3@3.879.0)':
|
||||||
|
dependencies:
|
||||||
|
'@aws-sdk/client-s3': 3.879.0
|
||||||
|
'@smithy/abort-controller': 4.2.8
|
||||||
|
'@smithy/middleware-endpoint': 4.4.13
|
||||||
|
'@smithy/smithy-client': 4.11.2
|
||||||
|
buffer: 5.6.0
|
||||||
|
events: 3.3.0
|
||||||
|
stream-browserify: 3.0.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@aws-sdk/middleware-bucket-endpoint@3.873.0':
|
'@aws-sdk/middleware-bucket-endpoint@3.873.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@aws-sdk/types': 3.862.0
|
'@aws-sdk/types': 3.862.0
|
||||||
@@ -11328,6 +11469,11 @@ snapshots:
|
|||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/abort-controller@4.2.8':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/chunked-blob-reader-native@4.0.0':
|
'@smithy/chunked-blob-reader-native@4.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/util-base64': 4.0.0
|
'@smithy/util-base64': 4.0.0
|
||||||
@@ -11345,6 +11491,19 @@ snapshots:
|
|||||||
'@smithy/util-middleware': 4.0.5
|
'@smithy/util-middleware': 4.0.5
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/core@3.22.1':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/middleware-serde': 4.2.9
|
||||||
|
'@smithy/protocol-http': 5.3.8
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
'@smithy/util-base64': 4.3.0
|
||||||
|
'@smithy/util-body-length-browser': 4.2.0
|
||||||
|
'@smithy/util-middleware': 4.2.8
|
||||||
|
'@smithy/util-stream': 4.5.11
|
||||||
|
'@smithy/util-utf8': 4.2.0
|
||||||
|
'@smithy/uuid': 1.1.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/core@3.9.0':
|
'@smithy/core@3.9.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/middleware-serde': 4.0.9
|
'@smithy/middleware-serde': 4.0.9
|
||||||
@@ -11405,6 +11564,14 @@ snapshots:
|
|||||||
'@smithy/util-base64': 4.0.0
|
'@smithy/util-base64': 4.0.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/fetch-http-handler@5.3.9':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/protocol-http': 5.3.8
|
||||||
|
'@smithy/querystring-builder': 4.2.8
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
'@smithy/util-base64': 4.3.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/hash-blob-browser@4.0.5':
|
'@smithy/hash-blob-browser@4.0.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/chunked-blob-reader': 5.0.0
|
'@smithy/chunked-blob-reader': 5.0.0
|
||||||
@@ -11438,6 +11605,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/is-array-buffer@4.2.0':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/md5-js@4.0.5':
|
'@smithy/md5-js@4.0.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
@@ -11461,6 +11632,17 @@ snapshots:
|
|||||||
'@smithy/util-middleware': 4.0.5
|
'@smithy/util-middleware': 4.0.5
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/middleware-endpoint@4.4.13':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/core': 3.22.1
|
||||||
|
'@smithy/middleware-serde': 4.2.9
|
||||||
|
'@smithy/node-config-provider': 4.3.8
|
||||||
|
'@smithy/shared-ini-file-loader': 4.4.3
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
'@smithy/url-parser': 4.2.8
|
||||||
|
'@smithy/util-middleware': 4.2.8
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/middleware-retry@4.1.20':
|
'@smithy/middleware-retry@4.1.20':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/node-config-provider': 4.1.4
|
'@smithy/node-config-provider': 4.1.4
|
||||||
@@ -11480,11 +11662,22 @@ snapshots:
|
|||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/middleware-serde@4.2.9':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/protocol-http': 5.3.8
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/middleware-stack@4.0.5':
|
'@smithy/middleware-stack@4.0.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/middleware-stack@4.2.8':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/node-config-provider@4.1.4':
|
'@smithy/node-config-provider@4.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/property-provider': 4.0.5
|
'@smithy/property-provider': 4.0.5
|
||||||
@@ -11492,6 +11685,13 @@ snapshots:
|
|||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/node-config-provider@4.3.8':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/property-provider': 4.2.8
|
||||||
|
'@smithy/shared-ini-file-loader': 4.4.3
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/node-http-handler@4.1.1':
|
'@smithy/node-http-handler@4.1.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/abort-controller': 4.0.5
|
'@smithy/abort-controller': 4.0.5
|
||||||
@@ -11500,27 +11700,56 @@ snapshots:
|
|||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/node-http-handler@4.4.9':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/abort-controller': 4.2.8
|
||||||
|
'@smithy/protocol-http': 5.3.8
|
||||||
|
'@smithy/querystring-builder': 4.2.8
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/property-provider@4.0.5':
|
'@smithy/property-provider@4.0.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/property-provider@4.2.8':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/protocol-http@5.1.3':
|
'@smithy/protocol-http@5.1.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/protocol-http@5.3.8':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/querystring-builder@4.0.5':
|
'@smithy/querystring-builder@4.0.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
'@smithy/util-uri-escape': 4.0.0
|
'@smithy/util-uri-escape': 4.0.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/querystring-builder@4.2.8':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
'@smithy/util-uri-escape': 4.2.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/querystring-parser@4.0.5':
|
'@smithy/querystring-parser@4.0.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/querystring-parser@4.2.8':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/service-error-classification@4.0.7':
|
'@smithy/service-error-classification@4.0.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
@@ -11530,6 +11759,11 @@ snapshots:
|
|||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/shared-ini-file-loader@4.4.3':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/signature-v4@5.1.3':
|
'@smithy/signature-v4@5.1.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/is-array-buffer': 4.0.0
|
'@smithy/is-array-buffer': 4.0.0
|
||||||
@@ -11541,6 +11775,16 @@ snapshots:
|
|||||||
'@smithy/util-utf8': 4.0.0
|
'@smithy/util-utf8': 4.0.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/smithy-client@4.11.2':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/core': 3.22.1
|
||||||
|
'@smithy/middleware-endpoint': 4.4.13
|
||||||
|
'@smithy/middleware-stack': 4.2.8
|
||||||
|
'@smithy/protocol-http': 5.3.8
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
'@smithy/util-stream': 4.5.11
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/smithy-client@4.5.0':
|
'@smithy/smithy-client@4.5.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/core': 3.9.0
|
'@smithy/core': 3.9.0
|
||||||
@@ -11551,6 +11795,10 @@ snapshots:
|
|||||||
'@smithy/util-stream': 4.2.4
|
'@smithy/util-stream': 4.2.4
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/types@4.12.0':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/types@4.3.2':
|
'@smithy/types@4.3.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
@@ -11561,16 +11809,32 @@ snapshots:
|
|||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/url-parser@4.2.8':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/querystring-parser': 4.2.8
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/util-base64@4.0.0':
|
'@smithy/util-base64@4.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/util-buffer-from': 4.0.0
|
'@smithy/util-buffer-from': 4.0.0
|
||||||
'@smithy/util-utf8': 4.0.0
|
'@smithy/util-utf8': 4.0.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/util-base64@4.3.0':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/util-buffer-from': 4.2.0
|
||||||
|
'@smithy/util-utf8': 4.2.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/util-body-length-browser@4.0.0':
|
'@smithy/util-body-length-browser@4.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/util-body-length-browser@4.2.0':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/util-body-length-node@4.0.0':
|
'@smithy/util-body-length-node@4.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
@@ -11585,6 +11849,11 @@ snapshots:
|
|||||||
'@smithy/is-array-buffer': 4.0.0
|
'@smithy/is-array-buffer': 4.0.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/util-buffer-from@4.2.0':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/is-array-buffer': 4.2.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/util-config-provider@4.0.0':
|
'@smithy/util-config-provider@4.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
@@ -11617,11 +11886,20 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/util-hex-encoding@4.2.0':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/util-middleware@4.0.5':
|
'@smithy/util-middleware@4.0.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/util-middleware@4.2.8':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/util-retry@4.0.7':
|
'@smithy/util-retry@4.0.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/service-error-classification': 4.0.7
|
'@smithy/service-error-classification': 4.0.7
|
||||||
@@ -11639,10 +11917,25 @@ snapshots:
|
|||||||
'@smithy/util-utf8': 4.0.0
|
'@smithy/util-utf8': 4.0.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/util-stream@4.5.11':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/fetch-http-handler': 5.3.9
|
||||||
|
'@smithy/node-http-handler': 4.4.9
|
||||||
|
'@smithy/types': 4.12.0
|
||||||
|
'@smithy/util-base64': 4.3.0
|
||||||
|
'@smithy/util-buffer-from': 4.2.0
|
||||||
|
'@smithy/util-hex-encoding': 4.2.0
|
||||||
|
'@smithy/util-utf8': 4.2.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/util-uri-escape@4.0.0':
|
'@smithy/util-uri-escape@4.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/util-uri-escape@4.2.0':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/util-utf8@2.3.0':
|
'@smithy/util-utf8@2.3.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/util-buffer-from': 2.2.0
|
'@smithy/util-buffer-from': 2.2.0
|
||||||
@@ -11653,12 +11946,21 @@ snapshots:
|
|||||||
'@smithy/util-buffer-from': 4.0.0
|
'@smithy/util-buffer-from': 4.0.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/util-utf8@4.2.0':
|
||||||
|
dependencies:
|
||||||
|
'@smithy/util-buffer-from': 4.2.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@smithy/util-waiter@4.0.7':
|
'@smithy/util-waiter@4.0.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@smithy/abort-controller': 4.0.5
|
'@smithy/abort-controller': 4.0.5
|
||||||
'@smithy/types': 4.3.2
|
'@smithy/types': 4.3.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@smithy/uuid@1.1.0':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@socket.io/component-emitter@3.1.2': {}
|
'@socket.io/component-emitter@3.1.2': {}
|
||||||
|
|
||||||
'@standard-schema/spec@1.0.0': {}
|
'@standard-schema/spec@1.0.0': {}
|
||||||
@@ -11919,6 +12221,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 2.26.1(@tiptap/pm@2.26.1)
|
'@tiptap/core': 2.26.1(@tiptap/pm@2.26.1)
|
||||||
|
|
||||||
|
'@tiptap/extension-typography@3.18.0(@tiptap/core@2.26.1(@tiptap/pm@2.26.1))':
|
||||||
|
dependencies:
|
||||||
|
'@tiptap/core': 2.26.1(@tiptap/pm@2.26.1)
|
||||||
|
|
||||||
'@tiptap/pm@2.26.1':
|
'@tiptap/pm@2.26.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
prosemirror-changeset: 2.3.1
|
prosemirror-changeset: 2.3.1
|
||||||
@@ -12778,6 +13084,11 @@ snapshots:
|
|||||||
|
|
||||||
buffer-from@1.1.2: {}
|
buffer-from@1.1.2: {}
|
||||||
|
|
||||||
|
buffer@5.6.0:
|
||||||
|
dependencies:
|
||||||
|
base64-js: 1.5.1
|
||||||
|
ieee754: 1.2.1
|
||||||
|
|
||||||
buffer@5.7.1:
|
buffer@5.7.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
base64-js: 1.5.1
|
base64-js: 1.5.1
|
||||||
@@ -16898,6 +17209,11 @@ snapshots:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
internal-slot: 1.1.0
|
internal-slot: 1.1.0
|
||||||
|
|
||||||
|
stream-browserify@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
inherits: 2.0.4
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
|
||||||
string-width@4.2.3:
|
string-width@4.2.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
emoji-regex: 8.0.0
|
emoji-regex: 8.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user