This commit is contained in:
Jordi Enric
2024-10-05 18:57:22 +02:00
parent ae896f3d29
commit 3fa93827b0
8 changed files with 468 additions and 240 deletions
+89 -14
View File
@@ -1,11 +1,47 @@
import { Hono } from "hono";
import { Context, Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
import { logger } from "hono/logger";
import { prettyJSON } from "hono/pretty-json";
import { handle } from "hono/vercel";
import { createClient } from "@/lib/server/supabase";
import { createId } from "@/lib/create-id";
export const dynamic = "force-dynamic";
import bcrypt from "bcrypt";
import { axiom } from "lib/axiom";
const UnauthorizedError = (c: Context) => {
return c.json({ message: "Unauthorized" }, { status: 401 });
};
const BlogNotFoundError = (c: Context) => {
return c.json({ message: "Blog not found" }, { status: 404 });
};
const ErrorRotatingAPIKey = (c: Context) => {
return c.json({ message: "Error rotating API key" }, { status: 500 });
};
const errors = {
UnauthorizedError,
BlogNotFoundError,
ErrorRotatingAPIKey,
};
const handleError = (c: Context, error: keyof typeof errors, rawLog: any) => {
console.log("🔴", error);
axiom.ingest("api", {
message: error,
error: true,
blogId: c.req.param("blogId"),
userId: c.req.param("userId"),
method: c.req.method,
path: c.req.url,
rawLog,
});
return errors[error](c);
};
const getUser = async () => {
const supabase = createClient();
@@ -17,26 +53,65 @@ const getUser = async () => {
};
};
const api = new Hono().post(
"/test",
zValidator(
"json",
z.object({
name: z.string().min(1),
})
),
async (c) => {
const api = new Hono()
// Rotates the API key for a blog
.post("/blogs/:blogId/api-keys/rotate", async (c) => {
const { user } = await getUser();
console.log("USER -->>", user?.email);
return c.json({ message: "test" });
}
);
const supabase = createClient();
if (!user) {
return UnauthorizedError(c);
}
// Check that the user is the owner of the blog
const { data: blog, error: blogError } = await supabase
.from("blogs")
.select("id")
.eq("id", c.req.param("blogId"))
.eq("user_id", user?.id)
.single();
if (blogError || !blog) {
return BlogNotFoundError(c);
}
// Create API key
const newAPIKey = createId({ type: "blog", secret: true });
const hashedAPIKey = await bcrypt.hash(newAPIKey, 10);
const { data, error } = await supabase
.from("blogs")
.update({
access_token: hashedAPIKey,
})
.eq("id", blog?.id);
if (error) {
console.log(error);
return handleError(c, "ErrorRotatingAPIKey", error);
}
// Return the new API key to the client for use / storage
return c.json({ message: "success", apiKey: newAPIKey }, 200);
});
const app = new Hono()
.basePath("/api")
// MIDDLEWARE
.use("*", logger())
.use("*", prettyJSON())
.use("*", async (c, next) => {
const start = Date.now();
await next();
const duration = Date.now() - start;
axiom.ingest("api", {
message: "Request completed",
duration,
method: c.req.method,
path: c.req.url,
status: c.res.status,
});
})
// ROUTES
.route("/v2", api);
+7
View File
@@ -0,0 +1,7 @@
import { Axiom } from "@axiomhq/js";
const axiom = new Axiom({
token: process.env.AXIOM_TOKEN,
});
export { axiom };
+3
View File
@@ -11,6 +11,7 @@
"supabase:typegen": "npx supabase gen types typescript --local --schema public > src/types/supabase.ts"
},
"dependencies": {
"@axiomhq/js": "^1.2.0",
"@builder.io/react-hydration-overlay": "^0.0.8",
"@chronark/zod-bird": "^0.3.9",
"@emoji-mart/data": "^1.1.2",
@@ -65,6 +66,7 @@
"@uidotdev/usehooks": "^2.0.1",
"@vercel/og": "^0.6.2",
"ai": "^3.1.30",
"bcrypt": "^5.1.1",
"browser-image-compression": "^2.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
@@ -112,6 +114,7 @@
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",
"@types/bcrypt": "^5.0.2",
"@types/eslint": "^8.21.3",
"@types/formidable": "^3.4.3",
"@types/lowlight": "^0.0.7",
+7 -5
View File
@@ -5,17 +5,19 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-semibold ring-offset-white transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-orange-200 focus-visible focus-visible:border-orange-700 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-zinc-950 ring-1 ring-transparent antialiased dark:focus-visible:ring-zinc-300 gap-1.5 [&>svg]:w-4 leading-none [&>svg]:opacity-60 active:scale-95",
"inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-semibold ring-offset-white transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-orange-200 focus-visible focus-visible:border-orange-700 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-zinc-950 ring-1 ring-transparent antialiased dark:focus-visible:ring-zinc-300 gap-1.5 [&>svg]:w-4 leading-none [&>svg]:opacity-60 active:scale-95 border border-transparent",
{
variants: {
variant: {
default:
"border border-slate-500/20 border-b-slate-800/70 border-t-slate-500/70 bg-gradient-to-b from-slate-700 to-slate-800 text-white shadow-md ring-slate-800 flex items-center hover:brightness-125",
destructive: "bg-red-500 text-zinc-50 hover:bg-red-500/90",
"border-slate-500/20 border-b-slate-800/70 border-t-slate-500/70 bg-gradient-to-b from-slate-700 to-slate-800 text-white shadow-md ring-slate-800 flex items-center hover:brightness-125",
destructive:
"border-red-400/20 border-b-red-700/70 border-t-red-400/70 bg-gradient-to-b from-red-500 to-red-500 text-white shadow-md ring-red-600 flex items-center hover:brightness-110",
outline:
"border border-transparent ring-zinc-200 bg-white hover:bg-zinc-100 hover:text-zinc-900 text-zinc-600",
"border-transparent ring-zinc-200 bg-white hover:bg-zinc-100 hover:text-zinc-900 text-zinc-600",
secondary:
"bg-zinc-100 hover:bg-orange-100/70 hover:text-orange-600 text-zinc-600",
"bg-zinc-100 hover:bg-zinc-100/70 text-zinc-700 hover:text-zinc-800",
ghost:
"hover:bg-zinc-100 hover:text-zinc-900 dark:hover:bg-zinc-800 dark:hover:text-zinc-50 text-zinc-600",
link: "text-zinc-800 underline-offset-4 hover:underline dark:text-zinc-50",
-179
View File
@@ -1,179 +0,0 @@
import { Blog, DeleteBlogRes, PatchBlog } from "@/lib/models/blogs/Blogs";
import { z } from "zod";
import { getPostBySlugRes, getPostsRes } from "../models/posts/Posts";
import { GetBlogRes } from "../models/blogs/Blogs";
import { JSONContent } from "@tiptap/react";
export type UpdatePostBody = {
title?: string;
slug?: string;
published?: boolean;
content?: JSONContent;
cover_image?: string;
};
export function createAPIClient() {
async function _fetch<T extends z.ZodTypeAny>(
input: RequestInfo,
init: RequestInit,
type: T
): Promise<z.infer<T>> {
const headers = new Headers(init?.headers);
const URL_START = "/api/v1";
const res = await fetch(URL_START + input, { ...init, headers });
if (!res.ok) {
console.error(`[🚨 ${init.method} ${input}] `, res.statusText);
throw new Error(res.statusText);
}
const data = await res.json();
const result = await type.safeParseAsync(data);
if (!result.success) {
console.error(`[ZOD] `, result.error.message, data);
throw new Error(result.error.message);
}
return result.data;
}
async function deleteBlog(slug: string) {
const res = await _fetch(
`/blogs/${slug}`,
{ method: "DELETE" },
DeleteBlogRes
);
return res;
}
async function patchBlog(slug: string, body: PatchBlog) {
const res = await _fetch(
`/blogs/${slug}`,
{ method: "PATCH", body: JSON.stringify(body) },
GetBlogRes
);
return res;
}
async function createBlog({
title,
description,
emoji,
}: {
title: string;
description: string;
emoji: string;
}) {
const res = await _fetch(
"/blogs",
{
method: "POST",
body: JSON.stringify({
title,
description,
emoji,
}),
},
GetBlogRes
);
return res;
}
async function getPostsForBlog(blogId: string) {
const res = await _fetch(
`/blogs/${blogId}/posts`,
{ method: "GET" },
getPostsRes
);
return res;
}
async function getPostBySlug(blogId: string, postSlug: string) {
const res = await _fetch(
`/blogs/${blogId}/posts/${postSlug}`,
{ method: "GET" },
getPostBySlugRes
);
return res;
}
async function deletePostBySlug(blogId: string, postSlug: string) {
const res = await _fetch(
`/blogs/${blogId}/posts/${postSlug}`,
{ method: "DELETE" },
z.object({ success: z.boolean() })
);
return res;
}
async function updatePostBySlug(
blogId: string,
postSlug: string,
body: UpdatePostBody
) {
const res = await _fetch(
`/blogs/${blogId}/posts/${postSlug}`,
{ method: "PATCH", body: JSON.stringify(body) },
z.object({ success: z.boolean() })
);
return res;
}
// TODO!: move these methods to separate files this is getting too big
return {
user: {
setup: () =>
_fetch(
"/user/setup",
{ method: "GET" },
z.object({ success: z.boolean() })
),
},
blogs: {
update: patchBlog,
delete: deleteBlog,
create: createBlog,
},
posts: {
getAll: getPostsForBlog,
get: getPostBySlug,
delete: deletePostBySlug,
update: updatePostBySlug,
},
images: {
getAll: (blogId: string) =>
_fetch(
`/blogs/${blogId}/images`,
{ method: "GET" },
z.array(
z.object({
id: z.string(),
name: z.string(),
url: z.string(),
createdAt: z.string(),
updatedAt: z.string(),
})
)
),
upload(blogId: string, file: File) {
const body = new FormData();
body.append("file", file);
return _fetch(
`/upload`,
{ method: "POST", body: JSON.stringify(body) },
z.any()
);
},
},
};
}
+75 -35
View File
@@ -17,6 +17,16 @@ import Link from "next/link";
import { getHostedBlogUrl } from "@/utils/get-hosted-blog-url";
import { Textarea } from "@/components/ui/textarea";
import { createId } from "@/lib/create-id";
import { API } from "app/utils/api-client";
import { useState } from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
} from "@/components/ui/dialog";
import { DialogTitle } from "@radix-ui/react-dialog";
export default function BlogSettings() {
type FormData = {
@@ -45,12 +55,6 @@ export default function BlogSettings() {
const updateBlog = useUpdateBlogMutation();
const accessTokenToShow = blog?.access_token
? blog.access_token.slice(0, 4) +
"......................" +
blog.access_token.slice(-4)
: "";
const onSubmit = handleSubmit(async (formData) => {
try {
console.log(formData);
@@ -90,11 +94,26 @@ export default function BlogSettings() {
}
}
const generateAccessToken = async () => {
const newId = createId({ type: "blog", secret: true });
await updateBlog.mutateAsync({ id: blogId, access_token: newId });
const [newAPIKey, setNewAPIKey] = useState("");
const [showNewAPIKeyDialog, setShowNewAPIKeyDialog] = useState(false);
const generateAPIKey = async () => {
const res = await API().v2.blogs[":blogId"]["api-keys"].rotate.$post({
param: {
blogId,
},
});
if (!res.ok) {
toast.error("Error generating api key");
return;
}
const data = (await res.json()) as { message: string; apiKey: string };
setNewAPIKey(data.apiKey);
setShowNewAPIKeyDialog(true);
refetchBlog();
toast.success("Access token created");
toast.success("API key created");
};
if (blogLoading) {
@@ -167,68 +186,89 @@ export default function BlogSettings() {
/>
</div>
<div className="actions">
{isDirty && <Button type="submit">Save</Button>}
<div className="actions mt-1">
<Button type="submit">Save</Button>
</div>
</form>
</div>
</section>
<section className="section p-3">
<h2 className="text-lg font-medium">API Access Tokens</h2>
<p>Use this token to fetch your blogs content through the API.</p>
<h2 className="text-lg font-medium">Blog API key</h2>
<p className="text-zinc-500">
Use this key to fetch your blogs content through the API.
</p>
<div className="mt-4 flex items-center gap-2">
{blog.access_token ? (
<div className="flex items-center gap-3">
<code className="truncate rounded-lg border px-2 py-1 font-mono text-sm">
{accessTokenToShow}
</code>
<Button
variant={"outline"}
onClick={() => {
navigator.clipboard.writeText(blog.access_token || "");
toast.success("API key copied to clipboard");
}}
>
Copy
</Button>
<div className="flex w-full items-center justify-end gap-3">
<Button
onClick={() => {
if (
confirm(
"Are you sure you want to regenerate the access token?\n The current one will stop working."
"Are you sure you want to rotate the API key?\n The current one will stop working."
)
) {
generateAccessToken();
generateAPIKey();
}
}}
variant={"destructive"}
>
Regenerate
Rotate API key
</Button>
</div>
) : (
<>
<Button
onClick={() => {
generateAccessToken();
generateAPIKey();
}}
variant={"outline"}
>
Generate Access Token
Generate API key
</Button>
</>
)}
</div>
<Dialog open={showNewAPIKeyDialog}>
<DialogContent className="flex max-w-sm flex-col gap-4">
<DialogHeader>
<DialogTitle>New API key</DialogTitle>
<DialogDescription>
Make sure to save this key in a secure location. <br /> It
will not be shown again.{" "}
</DialogDescription>
</DialogHeader>
<div className="flex items-center gap-2">
<Input
value={newAPIKey}
readOnly
className="flex-grow font-mono"
/>
<Button
variant={"outline"}
onClick={() => {
navigator.clipboard.writeText(newAPIKey || "");
toast.success("API key copied to clipboard");
}}
>
Copy
</Button>
</div>
<DialogFooter>
<Button onClick={() => setShowNewAPIKeyDialog(false)}>
I have saved the key securely
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</section>
<section className="section border bg-white p-3">
<h2 className="mb-4 flex items-center gap-2 text-lg font-medium text-red-600">
<AlertCircle size={18} className="text-red-600" />
Danger zone
</h2>
<p className="">
<p className="text-zinc-500">
This action cannot be undone. This will delete all posts in the
blog.
</p>
+4 -2
View File
@@ -4,7 +4,7 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { Button } from "@/components/ui/button";
import { PiPencilLine } from "react-icons/pi";
import { Paintbrush, Pencil, Plus, Settings } from "lucide-react";
import { Paintbrush, Plus, Settings } from "lucide-react";
import { useUser } from "@/utils/supabase/browser";
import { useState } from "react";
@@ -97,7 +97,9 @@ export default function Dashboard() {
<Settings size="24" />
</Button>
<Button
variant={hovering === blog.id ? "default" : "outline"}
variant={
hovering === blog.id ? "secondary" : "secondary"
}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
+283 -5
View File
@@ -567,6 +567,7 @@
"apps/web": {
"version": "0.1.0",
"dependencies": {
"@axiomhq/js": "^1.2.0",
"@builder.io/react-hydration-overlay": "^0.0.8",
"@chronark/zod-bird": "^0.3.9",
"@emoji-mart/data": "^1.1.2",
@@ -621,6 +622,7 @@
"@uidotdev/usehooks": "^2.0.1",
"@vercel/og": "^0.6.2",
"ai": "^3.1.30",
"bcrypt": "^5.1.1",
"browser-image-compression": "^2.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
@@ -668,6 +670,7 @@
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",
"@types/bcrypt": "^5.0.2",
"@types/eslint": "^8.21.3",
"@types/formidable": "^3.4.3",
"@types/lowlight": "^0.0.7",
@@ -1917,6 +1920,26 @@
"node": ">= 14"
}
},
"node_modules/@axiomhq/js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@axiomhq/js/-/js-1.2.0.tgz",
"integrity": "sha512-ALvNo8YeDwE7EHWr9TGijdfo/gbmgTB5XHHBcIcu6Ra+A9+2ltBO5UvTzvSSGPaxn4neEyh+8ESzEBJVEjNAnA==",
"dependencies": {
"fetch-retry": "^6.0.0",
"uuid": "^8.3.2"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@axiomhq/js/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@babel/code-frame": {
"version": "7.12.11",
"license": "MIT",
@@ -5842,6 +5865,124 @@
"@lezer/lr": "^1.4.0"
}
},
"node_modules/@mapbox/node-pre-gyp": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
"dependencies": {
"detect-libc": "^2.0.0",
"https-proxy-agent": "^5.0.0",
"make-dir": "^3.1.0",
"node-fetch": "^2.6.7",
"nopt": "^5.0.0",
"npmlog": "^5.0.1",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"tar": "^6.1.11"
},
"bin": {
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"node_modules/@mapbox/node-pre-gyp/node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"engines": {
"node": ">=10"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dependencies": {
"semver": "^6.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"dependencies": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/minizlib/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/tar": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^5.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@mdx-js/mdx": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.1.tgz",
@@ -12293,6 +12434,15 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/bcrypt": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz",
"integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -13730,6 +13880,17 @@
"node": ">=0.4.0"
}
},
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/agentkeepalive": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
@@ -13968,6 +14129,24 @@
"resolved": "apps/api",
"link": true
},
"node_modules/aproba": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
},
"node_modules/are-we-there-yet": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
"deprecated": "This package is no longer supported.",
"dependencies": {
"delegates": "^1.0.0",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/arg": {
"version": "5.0.2",
"license": "MIT"
@@ -14412,6 +14591,19 @@
],
"license": "MIT"
},
"node_modules/bcrypt": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
"integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==",
"hasInstallScript": true,
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
"node-addon-api": "^5.0.0"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/beautify": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/beautify/-/beautify-0.0.8.tgz",
@@ -15365,6 +15557,14 @@
"simple-swizzle": "^0.2.2"
}
},
"node_modules/color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"bin": {
"color-support": "bin.js"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"license": "MIT",
@@ -15517,6 +15717,11 @@
"node": "^14.18.0 || >=16.10.0"
}
},
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
},
"node_modules/constant-case": {
"version": "2.0.0",
"dev": true,
@@ -16168,6 +16373,11 @@
"node": ">=0.4.0"
}
},
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
},
"node_modules/depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -16222,7 +16432,6 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
"optional": true,
"engines": {
"node": ">=8"
}
@@ -18005,6 +18214,11 @@
"node": "^12.20 || >= 14.13"
}
},
"node_modules/fetch-retry": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-6.0.0.tgz",
"integrity": "sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag=="
},
"node_modules/fflate": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
@@ -18462,7 +18676,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"peer": true,
"dependencies": {
"minipass": "^3.0.0"
},
@@ -18474,7 +18687,6 @@
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"peer": true,
"dependencies": {
"yallist": "^4.0.0"
},
@@ -18545,6 +18757,26 @@
"node": ">=10"
}
},
"node_modules/gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
"deprecated": "This package is no longer supported.",
"dependencies": {
"aproba": "^1.0.3 || ^2.0.0",
"color-support": "^1.1.2",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.1",
"object-assign": "^4.1.1",
"signal-exit": "^3.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"wide-align": "^1.1.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"license": "MIT",
@@ -18977,6 +19209,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
"node_modules/hash": {
"resolved": "packages/hash",
"link": true
@@ -19728,6 +19965,18 @@
"node": ">= 0.6"
}
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/httpsnippet-lite": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/httpsnippet-lite/-/httpsnippet-lite-3.0.5.tgz",
@@ -22953,6 +23202,11 @@
"lower-case": "^1.1.1"
}
},
"node_modules/node-addon-api": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
},
"node_modules/node-dir": {
"version": "0.1.17",
"resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz",
@@ -23160,6 +23414,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/npmlog": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
"deprecated": "This package is no longer supported.",
"dependencies": {
"are-we-there-yet": "^2.0.0",
"console-control-strings": "^1.1.0",
"gauge": "^3.0.0",
"set-blocking": "^2.0.0"
}
},
"node_modules/nypm": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.9.tgz",
@@ -26609,6 +26875,11 @@
"node": ">= 0.8.0"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -30257,6 +30528,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"node_modules/widest-line": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
@@ -30439,8 +30718,7 @@
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"peer": true
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yaml": {
"version": "1.10.2",