feat: add migration

This commit is contained in:
Mauricio Siu
2025-02-09 18:19:21 -06:00
parent 6b9fd596e5
commit c04bf3c7e0
31 changed files with 5790 additions and 440 deletions

View File

@@ -0,0 +1,255 @@
CREATE TABLE "account" (
"id" text PRIMARY KEY NOT NULL,
"account_id" text NOT NULL,
"provider_id" text NOT NULL,
"user_id" text NOT NULL,
"access_token" text,
"refresh_token" text,
"id_token" text,
"access_token_expires_at" timestamp,
"refresh_token_expires_at" timestamp,
"scope" text,
"password" text,
"is2FAEnabled" boolean DEFAULT false NOT NULL,
"created_at" timestamp NOT NULL,
"updated_at" timestamp NOT NULL,
"resetPasswordToken" text,
"resetPasswordExpiresAt" text,
"confirmationToken" text,
"confirmationExpiresAt" text
);
--> statement-breakpoint
CREATE TABLE "verification" (
"id" text PRIMARY KEY NOT NULL,
"identifier" text NOT NULL,
"value" text NOT NULL,
"expires_at" timestamp NOT NULL,
"created_at" timestamp,
"updated_at" timestamp
);
-- Primero eliminar las restricciones NOT NULL y foreign keys
ALTER TABLE "user" ALTER COLUMN "adminId" DROP NOT NULL;
ALTER TABLE "user" ALTER COLUMN "authId" DROP NOT NULL;
ALTER TABLE "user" DROP CONSTRAINT IF EXISTS "user_adminId_admin_adminId_fk";
ALTER TABLE "user" DROP CONSTRAINT IF EXISTS "user_authId_auth_id_fk";
ALTER TABLE "admin" DROP CONSTRAINT IF EXISTS "admin_authId_auth_id_fk";
ALTER TABLE "project" DROP CONSTRAINT IF EXISTS "project_adminId_admin_adminId_fk";
ALTER TABLE "destination" DROP CONSTRAINT IF EXISTS "destination_adminId_admin_adminId_fk";
ALTER TABLE "certificate" DROP CONSTRAINT IF EXISTS "certificate_adminId_admin_adminId_fk";
ALTER TABLE "session" DROP CONSTRAINT IF EXISTS "session_user_id_auth_id_fk";
ALTER TABLE "registry" DROP CONSTRAINT IF EXISTS "registry_adminId_admin_adminId_fk";
ALTER TABLE "notification" DROP CONSTRAINT IF EXISTS "notification_adminId_admin_adminId_fk";
ALTER TABLE "ssh-key" DROP CONSTRAINT IF EXISTS "ssh-key_adminId_admin_adminId_fk";
ALTER TABLE "git_provider" DROP CONSTRAINT IF EXISTS "git_provider_adminId_admin_adminId_fk";
ALTER TABLE "server" DROP CONSTRAINT IF EXISTS "server_adminId_admin_adminId_fk";
-- Luego renombrar las columnas
ALTER TABLE "user" RENAME COLUMN "userId" TO "id";
ALTER TABLE "project" RENAME COLUMN "adminId" TO "userId";
ALTER TABLE "destination" RENAME COLUMN "adminId" TO "userId";
ALTER TABLE "certificate" RENAME COLUMN "adminId" TO "userId";
ALTER TABLE "registry" RENAME COLUMN "adminId" TO "userId";
ALTER TABLE "notification" RENAME COLUMN "adminId" TO "userId";
ALTER TABLE "ssh-key" RENAME COLUMN "adminId" TO "userId";
ALTER TABLE "git_provider" RENAME COLUMN "adminId" TO "userId";
ALTER TABLE "server" RENAME COLUMN "adminId" TO "userId";
-- Primero agregar todas las columnas sin restricciones
ALTER TABLE "user" ADD COLUMN "name" text;
ALTER TABLE "user" ADD COLUMN "email" text;
ALTER TABLE "user" ADD COLUMN "email_verified" boolean;
ALTER TABLE "user" ADD COLUMN "image" text;
ALTER TABLE "user" ADD COLUMN "role" text;
ALTER TABLE "user" ADD COLUMN "banned" boolean;
ALTER TABLE "user" ADD COLUMN "ban_reason" text;
ALTER TABLE "user" ADD COLUMN "ban_expires" timestamp;
ALTER TABLE "user" ADD COLUMN "updated_at" timestamp;
ALTER TABLE "user" ADD COLUMN "serverIp" text;
ALTER TABLE "user" ADD COLUMN "certificateType" "certificateType" DEFAULT 'none';
ALTER TABLE "user" ADD COLUMN "host" text;
ALTER TABLE "user" ADD COLUMN "letsEncryptEmail" text;
ALTER TABLE "user" ADD COLUMN "sshPrivateKey" text;
ALTER TABLE "user" ADD COLUMN "enableDockerCleanup" boolean DEFAULT false;
ALTER TABLE "user" ADD COLUMN "enableLogRotation" boolean DEFAULT false;
ALTER TABLE "user" ADD COLUMN "enablePaidFeatures" boolean DEFAULT false;
ALTER TABLE "user" ADD COLUMN "metricsConfig" jsonb DEFAULT '{"server":{"type":"Dokploy","refreshRate":60,"port":4500,"token":"","retentionDays":2,"cronJob":"","urlCallback":"","thresholds":{"cpu":0,"memory":0}},"containers":{"refreshRate":60,"services":{"include":[],"exclude":[]}}}';
ALTER TABLE "user" ADD COLUMN "cleanupCacheApplications" boolean DEFAULT false;
ALTER TABLE "user" ADD COLUMN "cleanupCacheOnPreviews" boolean DEFAULT false;
ALTER TABLE "user" ADD COLUMN "cleanupCacheOnCompose" boolean DEFAULT false;
ALTER TABLE "user" ALTER COLUMN "token" SET DEFAULT '';
ALTER TABLE "user" ALTER COLUMN "expirationDate" SET DEFAULT CURRENT_TIMESTAMP + INTERVAL '1 year';
ALTER TABLE "user" ALTER COLUMN "createdAt" SET DEFAULT to_char(CURRENT_TIMESTAMP, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"');
--> statement-breakpoint
-- Luego actualizar los valores nulos
UPDATE "user" SET token = '' WHERE token IS NULL;
UPDATE "user" SET "expirationDate" = CURRENT_TIMESTAMP + INTERVAL '1 year' WHERE "expirationDate" IS NULL;
UPDATE "user" SET "createdAt" = to_char(CURRENT_TIMESTAMP, 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') WHERE "createdAt" IS NULL;
UPDATE "user" SET "name" = '' WHERE "name" IS NULL;
UPDATE "user" SET "email" = COALESCE("email", '') WHERE true;
UPDATE "user" SET "email_verified" = COALESCE("email_verified", false) WHERE true;
UPDATE "user" SET "role" = COALESCE("role", 'user') WHERE true;
UPDATE "user" SET "banned" = COALESCE("banned", false) WHERE true;
UPDATE "user" SET "updated_at" = COALESCE("updated_at", CURRENT_TIMESTAMP) WHERE true;
UPDATE "user" SET "certificateType" = COALESCE("certificateType", 'none') WHERE true;
UPDATE "user" SET "enableDockerCleanup" = COALESCE("enableDockerCleanup", false) WHERE true;
UPDATE "user" SET "enableLogRotation" = COALESCE("enableLogRotation", false) WHERE true;
UPDATE "user" SET "enablePaidFeatures" = COALESCE("enablePaidFeatures", false) WHERE true;
UPDATE "user" SET "metricsConfig" = COALESCE("metricsConfig", '{"server":{"type":"Dokploy","refreshRate":60,"port":4500,"token":"","retentionDays":2,"cronJob":"","urlCallback":"","thresholds":{"cpu":0,"memory":0}},"containers":{"refreshRate":60,"services":{"include":[],"exclude":[]}}}') WHERE true;
UPDATE "user" SET "cleanupCacheApplications" = COALESCE("cleanupCacheApplications", false) WHERE true;
UPDATE "user" SET "cleanupCacheOnPreviews" = COALESCE("cleanupCacheOnPreviews", false) WHERE true;
UPDATE "user" SET "cleanupCacheOnCompose" = COALESCE("cleanupCacheOnCompose", false) WHERE true;
--> statement-breakpoint
-- Migrar datos de auth a user
INSERT INTO "user" (
id,
name,
email,
email_verified,
image,
role,
updated_at
)
SELECT
id,
'' as name,
email,
true as email_verified,
image,
CASE
WHEN rol = 'admin' THEN 'admin'
ELSE 'user'
END as role,
CAST("createdAt" AS timestamp) as updated_at
FROM "auth";
-- Migrar datos de admin a user
UPDATE "user" u
SET
"serverIp" = a."serverIp",
"certificateType" = a."certificateType",
"host" = a."host",
"letsEncryptEmail" = a."letsEncryptEmail",
"sshPrivateKey" = a."sshPrivateKey",
"enableDockerCleanup" = a."enableDockerCleanup",
"enableLogRotation" = a."enableLogRotation",
"enablePaidFeatures" = a."enablePaidFeatures",
"metricsConfig" = a."metricsConfig",
"cleanupCacheApplications" = a."cleanupCacheApplications",
"cleanupCacheOnPreviews" = a."cleanupCacheOnPreviews",
"cleanupCacheOnCompose" = a."cleanupCacheOnCompose"
FROM "admin" a
WHERE u.id = a."authId";
-- Actualizar referencias en las tablas relacionadas
UPDATE "project" p
SET "userId" = a."authId"
FROM "admin" a
WHERE p."userId" = a."adminId";
UPDATE "destination" d
SET "userId" = a."authId"
FROM "admin" a
WHERE d."userId" = a."adminId";
UPDATE "certificate" c
SET "userId" = a."authId"
FROM "admin" a
WHERE c."userId" = a."adminId";
UPDATE "registry" r
SET "userId" = a."authId"
FROM "admin" a
WHERE r."userId" = a."adminId";
UPDATE "notification" n
SET "userId" = a."authId"
FROM "admin" a
WHERE n."userId" = a."adminId";
UPDATE "ssh-key" s
SET "userId" = a."authId"
FROM "admin" a
WHERE s."userId" = a."adminId";
UPDATE "git_provider" g
SET "userId" = a."authId"
FROM "admin" a
WHERE g."userId" = a."adminId";
UPDATE "server" s
SET "userId" = a."authId"
FROM "admin" a
WHERE s."userId" = a."adminId";
-- Ahora agregar las restricciones NOT NULL después de migrar los datos
ALTER TABLE "user" ALTER COLUMN "name" SET NOT NULL;
ALTER TABLE "user" ALTER COLUMN "email" SET NOT NULL;
ALTER TABLE "user" ALTER COLUMN "email_verified" SET NOT NULL;
ALTER TABLE "user" ALTER COLUMN "updated_at" SET NOT NULL;
ALTER TABLE "user" ALTER COLUMN "certificateType" SET NOT NULL;
ALTER TABLE "user" ALTER COLUMN "enableDockerCleanup" SET NOT NULL;
ALTER TABLE "user" ALTER COLUMN "enableLogRotation" SET NOT NULL;
ALTER TABLE "user" ALTER COLUMN "enablePaidFeatures" SET NOT NULL;
ALTER TABLE "user" ALTER COLUMN "metricsConfig" SET NOT NULL;
ALTER TABLE "user" ALTER COLUMN "cleanupCacheApplications" SET NOT NULL;
ALTER TABLE "user" ALTER COLUMN "cleanupCacheOnPreviews" SET NOT NULL;
ALTER TABLE "user" ALTER COLUMN "cleanupCacheOnCompose" SET NOT NULL;
-- Modificar session
ALTER TABLE "session" ALTER COLUMN "expires_at" SET DATA TYPE timestamp;
ALTER TABLE "session" ADD COLUMN "token" text;
ALTER TABLE "session" ADD COLUMN "created_at" timestamp;
ALTER TABLE "session" ADD COLUMN "updated_at" timestamp;
ALTER TABLE "session" ADD COLUMN "ip_address" text;
ALTER TABLE "session" ADD COLUMN "user_agent" text;
ALTER TABLE "session" ADD COLUMN "impersonated_by" text;
-- Agregar nuevas restricciones después de migrar todos los datos
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;
ALTER TABLE "project" ADD CONSTRAINT "project_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "destination" ADD CONSTRAINT "destination_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "certificate" ADD CONSTRAINT "certificate_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;
ALTER TABLE "registry" ADD CONSTRAINT "registry_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "notification" ADD CONSTRAINT "notification_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "ssh-key" ADD CONSTRAINT "ssh-key_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "git_provider" ADD CONSTRAINT "git_provider_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "server" ADD CONSTRAINT "server_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
-- Agregar restricciones únicas
ALTER TABLE "user" ADD CONSTRAINT "user_email_unique" UNIQUE("email");
ALTER TABLE "session" ADD CONSTRAINT "session_token_unique" UNIQUE("token");
-- Eliminar columnas antiguas
ALTER TABLE "user" DROP COLUMN IF EXISTS "adminId";
ALTER TABLE "user" DROP COLUMN IF EXISTS "authId";
-- Eliminar columnas de admin
ALTER TABLE "admin" DROP COLUMN IF EXISTS "adminId";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "serverIp";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "certificateType";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "host";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "letsEncryptEmail";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "sshPrivateKey";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "enableDockerCleanup";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "enableLogRotation";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "authId";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "createdAt";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "stripeCustomerId";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "stripeSubscriptionId";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "serversQuantity";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "enablePaidFeatures";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "metricsConfig";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "cleanupCacheApplications";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "cleanupCacheOnPreviews";
ALTER TABLE "admin" DROP COLUMN IF EXISTS "cleanupCacheOnCompose";
-- Eliminar tablas antiguas
DROP TABLE IF EXISTS "auth" CASCADE;
DROP TABLE IF EXISTS "admin" CASCADE;

File diff suppressed because it is too large Load Diff

View File

@@ -463,6 +463,13 @@
"when": 1739087857244, "when": 1739087857244,
"tag": "0065_daily_zaladane", "tag": "0065_daily_zaladane",
"breakpoints": true "breakpoints": true
},
{
"idx": 66,
"version": "7",
"when": 1739142819089,
"tag": "0066_broad_marrow",
"breakpoints": true
} }
] ]
} }

4
apps/dokploy/lib/auth.ts Normal file
View File

@@ -0,0 +1,4 @@
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient({
baseURL: "http://localhost:3000", // the base url of your auth server
});

View File

@@ -35,6 +35,7 @@
"test": "vitest --config __test__/vitest.config.ts" "test": "vitest --config __test__/vitest.config.ts"
}, },
"dependencies": { "dependencies": {
"better-auth":"1.1.16",
"bl": "6.0.11", "bl": "6.0.11",
"rotating-file-stream": "3.2.3", "rotating-file-stream": "3.2.3",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",

View File

@@ -0,0 +1,7 @@
import { auth } from "@dokploy/server/index";
import { toNodeHandler } from "better-auth/node";
// Disallow body parsing, we will parse it manually
export const config = { api: { bodyParser: false } };
export default toNodeHandler(auth.handler);

View File

@@ -18,224 +18,224 @@ export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse, res: NextApiResponse,
) { ) {
if (!endpointSecret) { // if (!endpointSecret) {
return res.status(400).send("Webhook Error: Missing Stripe Secret Key"); // return res.status(400).send("Webhook Error: Missing Stripe Secret Key");
} // }
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { // const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2024-09-30.acacia", // apiVersion: "2024-09-30.acacia",
maxNetworkRetries: 3, // maxNetworkRetries: 3,
}); // });
const buf = await buffer(req); // const buf = await buffer(req);
const sig = req.headers["stripe-signature"] as string; // const sig = req.headers["stripe-signature"] as string;
let event: Stripe.Event; // let event: Stripe.Event;
try { // try {
event = stripe.webhooks.constructEvent(buf, sig, endpointSecret); // event = stripe.webhooks.constructEvent(buf, sig, endpointSecret);
} catch (err) { // } catch (err) {
console.error( // console.error(
"Webhook signature verification failed.", // "Webhook signature verification failed.",
err instanceof Error ? err.message : err, // err instanceof Error ? err.message : err,
); // );
return res.status(400).send("Webhook Error: "); // return res.status(400).send("Webhook Error: ");
} // }
const webhooksAllowed = [ // const webhooksAllowed = [
"customer.subscription.created", // "customer.subscription.created",
"customer.subscription.deleted", // "customer.subscription.deleted",
"customer.subscription.updated", // "customer.subscription.updated",
"invoice.payment_succeeded", // "invoice.payment_succeeded",
"invoice.payment_failed", // "invoice.payment_failed",
"customer.deleted", // "customer.deleted",
"checkout.session.completed", // "checkout.session.completed",
]; // ];
if (!webhooksAllowed.includes(event.type)) { // if (!webhooksAllowed.includes(event.type)) {
return res.status(400).send("Webhook Error: Invalid Event Type"); // return res.status(400).send("Webhook Error: Invalid Event Type");
} // }
switch (event.type) { // switch (event.type) {
case "checkout.session.completed": { // case "checkout.session.completed": {
const session = event.data.object as Stripe.Checkout.Session; // const session = event.data.object as Stripe.Checkout.Session;
const adminId = session?.metadata?.adminId as string; // const adminId = session?.metadata?.adminId as string;
const subscription = await stripe.subscriptions.retrieve( // const subscription = await stripe.subscriptions.retrieve(
session.subscription as string, // session.subscription as string,
); // );
await db // await db
.update(admins) // .update(admins)
.set({ // .set({
stripeCustomerId: session.customer as string, // stripeCustomerId: session.customer as string,
stripeSubscriptionId: session.subscription as string, // stripeSubscriptionId: session.subscription as string,
serversQuantity: subscription?.items?.data?.[0]?.quantity ?? 0, // serversQuantity: subscription?.items?.data?.[0]?.quantity ?? 0,
}) // })
.where(eq(admins.adminId, adminId)) // .where(eq(admins.adminId, adminId))
.returning(); // .returning();
const admin = await findAdminById(adminId); // const admin = await findAdminById(adminId);
if (!admin) { // if (!admin) {
return res.status(400).send("Webhook Error: Admin not found"); // return res.status(400).send("Webhook Error: Admin not found");
} // }
const newServersQuantity = admin.serversQuantity; // const newServersQuantity = admin.serversQuantity;
await updateServersBasedOnQuantity(admin.adminId, newServersQuantity); // await updateServersBasedOnQuantity(admin.adminId, newServersQuantity);
break; // break;
} // }
case "customer.subscription.created": { // case "customer.subscription.created": {
const newSubscription = event.data.object as Stripe.Subscription; // const newSubscription = event.data.object as Stripe.Subscription;
await db // await db
.update(admins) // .update(admins)
.set({ // .set({
stripeSubscriptionId: newSubscription.id, // stripeSubscriptionId: newSubscription.id,
stripeCustomerId: newSubscription.customer as string, // stripeCustomerId: newSubscription.customer as string,
}) // })
.where(eq(admins.stripeCustomerId, newSubscription.customer as string)) // .where(eq(admins.stripeCustomerId, newSubscription.customer as string))
.returning(); // .returning();
break; // break;
} // }
case "customer.subscription.deleted": { // case "customer.subscription.deleted": {
const newSubscription = event.data.object as Stripe.Subscription; // const newSubscription = event.data.object as Stripe.Subscription;
await db // await db
.update(admins) // .update(admins)
.set({ // .set({
stripeSubscriptionId: null, // stripeSubscriptionId: null,
serversQuantity: 0, // serversQuantity: 0,
}) // })
.where(eq(admins.stripeCustomerId, newSubscription.customer as string)); // .where(eq(admins.stripeCustomerId, newSubscription.customer as string));
const admin = await findAdminByStripeCustomerId( // const admin = await findAdminByStripeCustomerId(
newSubscription.customer as string, // newSubscription.customer as string,
); // );
if (!admin) { // if (!admin) {
return res.status(400).send("Webhook Error: Admin not found"); // return res.status(400).send("Webhook Error: Admin not found");
} // }
await disableServers(admin.adminId); // await disableServers(admin.adminId);
break; // break;
} // }
case "customer.subscription.updated": { // case "customer.subscription.updated": {
const newSubscription = event.data.object as Stripe.Subscription; // const newSubscription = event.data.object as Stripe.Subscription;
const admin = await findAdminByStripeCustomerId( // const admin = await findAdminByStripeCustomerId(
newSubscription.customer as string, // newSubscription.customer as string,
); // );
if (!admin) { // if (!admin) {
return res.status(400).send("Webhook Error: Admin not found"); // return res.status(400).send("Webhook Error: Admin not found");
} // }
if (newSubscription.status === "active") { // if (newSubscription.status === "active") {
await db // await db
.update(admins) // .update(admins)
.set({ // .set({
serversQuantity: newSubscription?.items?.data?.[0]?.quantity ?? 0, // serversQuantity: newSubscription?.items?.data?.[0]?.quantity ?? 0,
}) // })
.where( // .where(
eq(admins.stripeCustomerId, newSubscription.customer as string), // eq(admins.stripeCustomerId, newSubscription.customer as string),
); // );
const newServersQuantity = admin.serversQuantity; // const newServersQuantity = admin.serversQuantity;
await updateServersBasedOnQuantity(admin.adminId, newServersQuantity); // await updateServersBasedOnQuantity(admin.adminId, newServersQuantity);
} else { // } else {
await disableServers(admin.adminId); // await disableServers(admin.adminId);
await db // await db
.update(admins) // .update(admins)
.set({ serversQuantity: 0 }) // .set({ serversQuantity: 0 })
.where( // .where(
eq(admins.stripeCustomerId, newSubscription.customer as string), // eq(admins.stripeCustomerId, newSubscription.customer as string),
); // );
} // }
break; // break;
} // }
case "invoice.payment_succeeded": { // case "invoice.payment_succeeded": {
const newInvoice = event.data.object as Stripe.Invoice; // const newInvoice = event.data.object as Stripe.Invoice;
const suscription = await stripe.subscriptions.retrieve( // const suscription = await stripe.subscriptions.retrieve(
newInvoice.subscription as string, // newInvoice.subscription as string,
); // );
if (suscription.status !== "active") { // if (suscription.status !== "active") {
console.log( // console.log(
`Skipping invoice.payment_succeeded for subscription ${suscription.id} with status ${suscription.status}`, // `Skipping invoice.payment_succeeded for subscription ${suscription.id} with status ${suscription.status}`,
); // );
break; // break;
} // }
await db // await db
.update(admins) // .update(admins)
.set({ // .set({
serversQuantity: suscription?.items?.data?.[0]?.quantity ?? 0, // serversQuantity: suscription?.items?.data?.[0]?.quantity ?? 0,
}) // })
.where(eq(admins.stripeCustomerId, suscription.customer as string)); // .where(eq(admins.stripeCustomerId, suscription.customer as string));
const admin = await findAdminByStripeCustomerId( // const admin = await findAdminByStripeCustomerId(
suscription.customer as string, // suscription.customer as string,
); // );
if (!admin) { // if (!admin) {
return res.status(400).send("Webhook Error: Admin not found"); // return res.status(400).send("Webhook Error: Admin not found");
} // }
const newServersQuantity = admin.serversQuantity; // const newServersQuantity = admin.serversQuantity;
await updateServersBasedOnQuantity(admin.adminId, newServersQuantity); // await updateServersBasedOnQuantity(admin.adminId, newServersQuantity);
break; // break;
} // }
case "invoice.payment_failed": { // case "invoice.payment_failed": {
const newInvoice = event.data.object as Stripe.Invoice; // const newInvoice = event.data.object as Stripe.Invoice;
const subscription = await stripe.subscriptions.retrieve( // const subscription = await stripe.subscriptions.retrieve(
newInvoice.subscription as string, // newInvoice.subscription as string,
); // );
if (subscription.status !== "active") { // if (subscription.status !== "active") {
const admin = await findAdminByStripeCustomerId( // const admin = await findAdminByStripeCustomerId(
newInvoice.customer as string, // newInvoice.customer as string,
); // );
if (!admin) { // if (!admin) {
return res.status(400).send("Webhook Error: Admin not found"); // return res.status(400).send("Webhook Error: Admin not found");
} // }
await db // await db
.update(admins) // .update(admins)
.set({ // .set({
serversQuantity: 0, // serversQuantity: 0,
}) // })
.where(eq(admins.stripeCustomerId, newInvoice.customer as string)); // .where(eq(admins.stripeCustomerId, newInvoice.customer as string));
await disableServers(admin.adminId); // await disableServers(admin.adminId);
} // }
break; // break;
} // }
case "customer.deleted": { // case "customer.deleted": {
const customer = event.data.object as Stripe.Customer; // const customer = event.data.object as Stripe.Customer;
const admin = await findAdminByStripeCustomerId(customer.id); // const admin = await findAdminByStripeCustomerId(customer.id);
if (!admin) { // if (!admin) {
return res.status(400).send("Webhook Error: Admin not found"); // return res.status(400).send("Webhook Error: Admin not found");
} // }
await disableServers(admin.adminId); // await disableServers(admin.adminId);
await db // await db
.update(admins) // .update(admins)
.set({ // .set({
stripeCustomerId: null, // stripeCustomerId: null,
stripeSubscriptionId: null, // stripeSubscriptionId: null,
serversQuantity: 0, // serversQuantity: 0,
}) // })
.where(eq(admins.stripeCustomerId, customer.id)); // .where(eq(admins.stripeCustomerId, customer.id));
break; // break;
} // }
default: // default:
console.log(`Unhandled event type: ${event.type}`); // console.log(`Unhandled event type: ${event.type}`);
} // }
return res.status(200).json({ received: true }); return res.status(200).json({ received: true });
} }

View File

@@ -70,9 +70,9 @@ import type {
} from "next"; } from "next";
import Head from "next/head"; import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useMemo, useState, type ReactElement } from "react"; import { type ReactElement, useMemo, useState } from "react";
import superjson from "superjson";
import { toast } from "sonner"; import { toast } from "sonner";
import superjson from "superjson";
export type Services = { export type Services = {
appName: string; appName: string;

View File

@@ -201,51 +201,51 @@ Home.getLayout = (page: ReactElement) => {
return <OnboardingLayout>{page}</OnboardingLayout>; return <OnboardingLayout>{page}</OnboardingLayout>;
}; };
export async function getServerSideProps(context: GetServerSidePropsContext) { export async function getServerSideProps(context: GetServerSidePropsContext) {
if (IS_CLOUD) { // if (IS_CLOUD) {
try { // try {
const { user } = await validateRequest(context.req, context.res); // const { user } = await validateRequest(context.req, context.res);
if (user) { // if (user) {
return { // return {
redirect: { // redirect: {
permanent: true, // permanent: true,
destination: "/dashboard/projects", // destination: "/dashboard/projects",
}, // },
}; // };
} // }
} catch (error) {} // } catch (error) {}
return { // return {
props: { // props: {
IS_CLOUD: IS_CLOUD, // IS_CLOUD: IS_CLOUD,
}, // },
}; // };
} // }
const hasAdmin = await isAdminPresent(); // const hasAdmin = await isAdminPresent();
if (!hasAdmin) { // if (!hasAdmin) {
return { // return {
redirect: { // redirect: {
permanent: true, // permanent: true,
destination: "/register", // destination: "/register",
}, // },
}; // };
} // }
const { user } = await validateRequest(context.req, context.res); // const { user } = await validateRequest(context.req, context.res);
if (user) { // if (user) {
return { // return {
redirect: { // redirect: {
permanent: true, // permanent: true,
destination: "/dashboard/projects", // destination: "/dashboard/projects",
}, // },
}; // };
} // }
return { return {
props: { props: {
hasAdmin, // hasAdmin,
}, },
}; };
} }

View File

@@ -34,14 +34,14 @@ void app.prepare().then(async () => {
}); });
// WEBSOCKET // WEBSOCKET
setupDrawerLogsWebSocketServer(server); // setupDrawerLogsWebSocketServer(server);
setupDeploymentLogsWebSocketServer(server); // setupDeploymentLogsWebSocketServer(server);
setupDockerContainerLogsWebSocketServer(server); // setupDockerContainerLogsWebSocketServer(server);
setupDockerContainerTerminalWebSocketServer(server); // setupDockerContainerTerminalWebSocketServer(server);
setupTerminalWebSocketServer(server); // setupTerminalWebSocketServer(server);
if (!IS_CLOUD) { // if (!IS_CLOUD) {
setupDockerStatsMonitoringSocketServer(server); // setupDockerStatsMonitoringSocketServer(server);
} // }
if (process.env.NODE_ENV === "production" && !IS_CLOUD) { if (process.env.NODE_ENV === "production" && !IS_CLOUD) {
setupDirectories(); setupDirectories();

View File

@@ -0,0 +1,62 @@
import {
pgTable,
text,
integer,
timestamp,
boolean,
} from "drizzle-orm/pg-core";
export const user = pgTable("user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified").notNull(),
image: text("image"),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
role: text("role"),
banned: boolean("banned"),
banReason: text("ban_reason"),
banExpires: timestamp("ban_expires"),
});
export const session = pgTable("session", {
id: text("id").primaryKey(),
expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
.references(() => user.id),
impersonatedBy: text("impersonated_by"),
});
// export const account = pgTable("account", {
// id: text("id").primaryKey(),
// accountId: text("account_id").notNull(),
// providerId: text("provider_id").notNull(),
// userId: text("user_id")
// .notNull()
// .references(() => user.id),
// accessToken: text("access_token"),
// refreshToken: text("refresh_token"),
// idToken: text("id_token"),
// accessTokenExpiresAt: timestamp("access_token_expires_at"),
// refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
// scope: text("scope"),
// password: text("password"),
// createdAt: timestamp("created_at").notNull(),
// updatedAt: timestamp("updated_at").notNull(),
// });
// export const verification = pgTable("verification", {
// id: text("id").primaryKey(),
// identifier: text("identifier").notNull(),
// value: text("value").notNull(),
// expiresAt: timestamp("expires_at").notNull(),
// createdAt: timestamp("created_at"),
// updatedAt: timestamp("updated_at"),
// });

View File

@@ -28,6 +28,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"better-auth":"1.1.16",
"rotating-file-stream": "3.2.3", "rotating-file-stream": "3.2.3",
"@faker-js/faker": "^8.4.1", "@faker-js/faker": "^8.4.1",
"@lucia-auth/adapter-drizzle": "1.0.7", "@lucia-auth/adapter-drizzle": "1.0.7",

View File

@@ -0,0 +1,34 @@
import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { users } from "./user";
export const account = pgTable("account", {
id: text("id").primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
userId: text("user_id")
.notNull()
.references(() => users.id),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
idToken: text("id_token"),
accessTokenExpiresAt: timestamp("access_token_expires_at"),
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
scope: text("scope"),
password: text("password"),
is2FAEnabled: boolean("is2FAEnabled").notNull().default(false),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
resetPasswordToken: text("resetPasswordToken"),
resetPasswordExpiresAt: text("resetPasswordExpiresAt"),
confirmationToken: text("confirmationToken"),
confirmationExpiresAt: text("confirmationExpiresAt"),
});
export const verification = pgTable("verification", {
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at"),
updatedAt: timestamp("updated_at"),
});

View File

@@ -18,128 +18,127 @@ import { sshKeys } from "./ssh-key";
import { users } from "./user"; import { users } from "./user";
export const admins = pgTable("admin", { export const admins = pgTable("admin", {
adminId: text("adminId") // adminId: text("adminId")
.notNull() // .notNull()
.primaryKey() // .primaryKey()
.$defaultFn(() => nanoid()), // .$defaultFn(() => nanoid()),
serverIp: text("serverIp"), // serverIp: text("serverIp"),
certificateType: certificateType("certificateType").notNull().default("none"), // certificateType: certificateType("certificateType").notNull().default("none"),
host: text("host"), // host: text("host"),
letsEncryptEmail: text("letsEncryptEmail"), // letsEncryptEmail: text("letsEncryptEmail"),
sshPrivateKey: text("sshPrivateKey"), // sshPrivateKey: text("sshPrivateKey"),
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false), // enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
enableLogRotation: boolean("enableLogRotation").notNull().default(false), // enableLogRotation: boolean("enableLogRotation").notNull().default(false),
authId: text("authId") // authId: text("authId")
.notNull() // .notNull()
.references(() => auth.id, { onDelete: "cascade" }), // .references(() => auth.id, { onDelete: "cascade" }),
createdAt: text("createdAt") // createdAt: text("createdAt")
.notNull() // .notNull()
.$defaultFn(() => new Date().toISOString()), // .$defaultFn(() => new Date().toISOString()),
stripeCustomerId: text("stripeCustomerId"), // stripeCustomerId: text("stripeCustomerId"),
stripeSubscriptionId: text("stripeSubscriptionId"), // stripeSubscriptionId: text("stripeSubscriptionId"),
serversQuantity: integer("serversQuantity").notNull().default(0), // serversQuantity: integer("serversQuantity").notNull().default(0),
// // Metrics
// Metrics // enablePaidFeatures: boolean("enablePaidFeatures").notNull().default(false),
enablePaidFeatures: boolean("enablePaidFeatures").notNull().default(false), // metricsConfig: jsonb("metricsConfig")
metricsConfig: jsonb("metricsConfig") // .$type<{
.$type<{ // server: {
server: { // type: "Dokploy" | "Remote";
type: "Dokploy" | "Remote"; // refreshRate: number;
refreshRate: number; // port: number;
port: number; // token: string;
token: string; // urlCallback: string;
urlCallback: string; // retentionDays: number;
retentionDays: number; // cronJob: string;
cronJob: string; // thresholds: {
thresholds: { // cpu: number;
cpu: number; // memory: number;
memory: number; // };
}; // };
}; // containers: {
containers: { // refreshRate: number;
refreshRate: number; // services: {
services: { // include: string[];
include: string[]; // exclude: string[];
exclude: string[]; // };
}; // };
}; // }>()
}>() // .notNull()
.notNull() // .default({
.default({ // server: {
server: { // type: "Dokploy",
type: "Dokploy", // refreshRate: 60,
refreshRate: 60, // port: 4500,
port: 4500, // token: "",
token: "", // retentionDays: 2,
retentionDays: 2, // cronJob: "",
cronJob: "", // urlCallback: "",
urlCallback: "", // thresholds: {
thresholds: { // cpu: 0,
cpu: 0, // memory: 0,
memory: 0, // },
}, // },
}, // containers: {
containers: { // refreshRate: 60,
refreshRate: 60, // services: {
services: { // include: [],
include: [], // exclude: [],
exclude: [], // },
}, // },
}, // }),
}), // cleanupCacheApplications: boolean("cleanupCacheApplications")
cleanupCacheApplications: boolean("cleanupCacheApplications") // .notNull()
.notNull() // .default(false),
.default(false), // cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews")
cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews") // .notNull()
.notNull() // .default(false),
.default(false), // cleanupCacheOnCompose: boolean("cleanupCacheOnCompose")
cleanupCacheOnCompose: boolean("cleanupCacheOnCompose") // .notNull()
.notNull() // .default(false),
.default(false),
}); });
export const adminsRelations = relations(admins, ({ one, many }) => ({ export const adminsRelations = relations(admins, ({ one, many }) => ({
auth: one(auth, { // auth: one(auth, {
fields: [admins.authId], // fields: [admins.authId],
references: [auth.id], // references: [auth.id],
}), // }),
users: many(users), // users: many(users),
registry: many(registry), // registry: many(registry),
sshKeys: many(sshKeys), // sshKeys: many(sshKeys),
certificates: many(certificates), // certificates: many(certificates),
})); }));
const createSchema = createInsertSchema(admins, { const createSchema = createInsertSchema(admins, {
adminId: z.string(), // adminId: z.string(),
enableDockerCleanup: z.boolean().optional(), // enableDockerCleanup: z.boolean().optional(),
sshPrivateKey: z.string().optional(), // sshPrivateKey: z.string().optional(),
certificateType: z.enum(["letsencrypt", "none"]).default("none"), // certificateType: z.enum(["letsencrypt", "none"]).default("none"),
serverIp: z.string().optional(), // serverIp: z.string().optional(),
letsEncryptEmail: z.string().optional(), // letsEncryptEmail: z.string().optional(),
}); });
export const apiUpdateAdmin = createSchema.partial(); export const apiUpdateAdmin = createSchema.partial();
export const apiSaveSSHKey = createSchema export const apiSaveSSHKey = createSchema
.pick({ .pick({
sshPrivateKey: true, // sshPrivateKey: true,
}) })
.required(); .required();
export const apiAssignDomain = createSchema export const apiAssignDomain = createSchema
.pick({ .pick({
host: true, // host: true,
certificateType: true, // certificateType: true,
letsEncryptEmail: true, // letsEncryptEmail: true,
}) })
.required() .required()
.partial({ .partial({
letsEncryptEmail: true, // letsEncryptEmail: true,
}); });
export const apiUpdateDockerCleanup = createSchema export const apiUpdateDockerCleanup = createSchema
.pick({ .pick({
enableDockerCleanup: true, // enableDockerCleanup: true,
}) })
.required() .required()
.extend({ .extend({

View File

@@ -4,7 +4,7 @@ import { boolean, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { admins } from "./admin"; // import { admins } from "./admin";
import { users } from "./user"; import { users } from "./user";
const randomImages = [ const randomImages = [
@@ -55,7 +55,7 @@ export const auth = pgTable("auth", {
}); });
export const authRelations = relations(auth, ({ many }) => ({ export const authRelations = relations(auth, ({ many }) => ({
admins: many(admins), // admins: many(admins),
users: many(users), users: many(users),
})); }));
const createSchema = createInsertSchema(auth, { const createSchema = createInsertSchema(auth, {

View File

@@ -3,9 +3,9 @@ import { boolean, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { admins } from "./admin";
import { server } from "./server"; import { server } from "./server";
import { generateAppName } from "./utils"; import { generateAppName } from "./utils";
import { users } from "./user";
export const certificates = pgTable("certificate", { export const certificates = pgTable("certificate", {
certificateId: text("certificateId") certificateId: text("certificateId")
@@ -20,7 +20,7 @@ export const certificates = pgTable("certificate", {
.$defaultFn(() => generateAppName("certificate")) .$defaultFn(() => generateAppName("certificate"))
.unique(), .unique(),
autoRenew: boolean("autoRenew"), autoRenew: boolean("autoRenew"),
adminId: text("adminId").references(() => admins.adminId, { userId: text("userId").references(() => users.id, {
onDelete: "cascade", onDelete: "cascade",
}), }),
serverId: text("serverId").references(() => server.serverId, { serverId: text("serverId").references(() => server.serverId, {
@@ -35,9 +35,9 @@ export const certificatesRelations = relations(
fields: [certificates.serverId], fields: [certificates.serverId],
references: [server.serverId], references: [server.serverId],
}), }),
admin: one(admins, { user: one(users, {
fields: [certificates.adminId], fields: [certificates.userId],
references: [admins.adminId], references: [users.id],
}), }),
}), }),
); );

View File

@@ -5,6 +5,7 @@ import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { admins } from "./admin"; import { admins } from "./admin";
import { backups } from "./backups"; import { backups } from "./backups";
import { users } from "./user";
export const destinations = pgTable("destination", { export const destinations = pgTable("destination", {
destinationId: text("destinationId") destinationId: text("destinationId")
@@ -19,18 +20,18 @@ export const destinations = pgTable("destination", {
region: text("region").notNull(), region: text("region").notNull(),
// maybe it can be null // maybe it can be null
endpoint: text("endpoint").notNull(), endpoint: text("endpoint").notNull(),
adminId: text("adminId") userId: text("userId")
.notNull() .notNull()
.references(() => admins.adminId, { onDelete: "cascade" }), .references(() => users.id, { onDelete: "cascade" }),
}); });
export const destinationsRelations = relations( export const destinationsRelations = relations(
destinations, destinations,
({ many, one }) => ({ ({ many, one }) => ({
backups: many(backups), backups: many(backups),
admin: one(admins, { user: one(users, {
fields: [destinations.adminId], fields: [destinations.userId],
references: [admins.adminId], references: [users.id],
}), }),
}), }),
); );

View File

@@ -7,6 +7,7 @@ import { admins } from "./admin";
import { bitbucket } from "./bitbucket"; import { bitbucket } from "./bitbucket";
import { github } from "./github"; import { github } from "./github";
import { gitlab } from "./gitlab"; import { gitlab } from "./gitlab";
import { users } from "./user";
export const gitProviderType = pgEnum("gitProviderType", [ export const gitProviderType = pgEnum("gitProviderType", [
"github", "github",
@@ -24,7 +25,7 @@ export const gitProvider = pgTable("git_provider", {
createdAt: text("createdAt") createdAt: text("createdAt")
.notNull() .notNull()
.$defaultFn(() => new Date().toISOString()), .$defaultFn(() => new Date().toISOString()),
adminId: text("adminId").references(() => admins.adminId, { userId: text("userId").references(() => users.id, {
onDelete: "cascade", onDelete: "cascade",
}), }),
}); });
@@ -42,9 +43,9 @@ export const gitProviderRelations = relations(gitProvider, ({ one, many }) => ({
fields: [gitProvider.gitProviderId], fields: [gitProvider.gitProviderId],
references: [bitbucket.gitProviderId], references: [bitbucket.gitProviderId],
}), }),
admin: one(admins, { user: one(users, {
fields: [gitProvider.adminId], fields: [gitProvider.userId],
references: [admins.adminId], references: [users.id],
}), }),
})); }));

View File

@@ -30,3 +30,4 @@ export * from "./gitlab";
export * from "./server"; export * from "./server";
export * from "./utils"; export * from "./utils";
export * from "./preview-deployments"; export * from "./preview-deployments";
export * from "./account";

View File

@@ -4,6 +4,7 @@ import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { admins } from "./admin"; import { admins } from "./admin";
import { users } from "./user";
export const notificationType = pgEnum("notificationType", [ export const notificationType = pgEnum("notificationType", [
"slack", "slack",
@@ -44,7 +45,7 @@ export const notifications = pgTable("notification", {
gotifyId: text("gotifyId").references(() => gotify.gotifyId, { gotifyId: text("gotifyId").references(() => gotify.gotifyId, {
onDelete: "cascade", onDelete: "cascade",
}), }),
adminId: text("adminId").references(() => admins.adminId, { userId: text("userId").references(() => users.id, {
onDelete: "cascade", onDelete: "cascade",
}), }),
}); });
@@ -121,9 +122,9 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
fields: [notifications.gotifyId], fields: [notifications.gotifyId],
references: [gotify.gotifyId], references: [gotify.gotifyId],
}), }),
admin: one(admins, { user: one(users, {
fields: [notifications.adminId], fields: [notifications.userId],
references: [admins.adminId], references: [users.id],
}), }),
})); }));

View File

@@ -4,7 +4,7 @@ import { pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { admins } from "./admin"; // import { admins } from "./admin";
import { applications } from "./application"; import { applications } from "./application";
import { compose } from "./compose"; import { compose } from "./compose";
import { mariadb } from "./mariadb"; import { mariadb } from "./mariadb";
@@ -12,6 +12,7 @@ import { mongo } from "./mongo";
import { mysql } from "./mysql"; import { mysql } from "./mysql";
import { postgres } from "./postgres"; import { postgres } from "./postgres";
import { redis } from "./redis"; import { redis } from "./redis";
import { users } from "./user";
export const projects = pgTable("project", { export const projects = pgTable("project", {
projectId: text("projectId") projectId: text("projectId")
@@ -23,9 +24,9 @@ export const projects = pgTable("project", {
createdAt: text("createdAt") createdAt: text("createdAt")
.notNull() .notNull()
.$defaultFn(() => new Date().toISOString()), .$defaultFn(() => new Date().toISOString()),
adminId: text("adminId") userId: text("userId")
.notNull() .notNull()
.references(() => admins.adminId, { onDelete: "cascade" }), .references(() => users.id, { onDelete: "cascade" }),
env: text("env").notNull().default(""), env: text("env").notNull().default(""),
}); });
@@ -37,9 +38,9 @@ export const projectRelations = relations(projects, ({ many, one }) => ({
mongo: many(mongo), mongo: many(mongo),
redis: many(redis), redis: many(redis),
compose: many(compose), compose: many(compose),
admin: one(admins, { user: one(users, {
fields: [projects.adminId], fields: [projects.userId],
references: [admins.adminId], references: [users.id],
}), }),
})); }));

View File

@@ -5,6 +5,7 @@ import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { admins } from "./admin"; import { admins } from "./admin";
import { applications } from "./application"; import { applications } from "./application";
import { users } from "./user";
/** /**
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
* database instance for multiple projects. * database instance for multiple projects.
@@ -27,15 +28,15 @@ export const registry = pgTable("registry", {
.notNull() .notNull()
.$defaultFn(() => new Date().toISOString()), .$defaultFn(() => new Date().toISOString()),
registryType: registryType("selfHosted").notNull().default("cloud"), registryType: registryType("selfHosted").notNull().default("cloud"),
adminId: text("adminId") userId: text("userId")
.notNull() .notNull()
.references(() => admins.adminId, { onDelete: "cascade" }), .references(() => users.id, { onDelete: "cascade" }),
}); });
export const registryRelations = relations(registry, ({ one, many }) => ({ export const registryRelations = relations(registry, ({ one, many }) => ({
admin: one(admins, { user: one(users, {
fields: [registry.adminId], fields: [registry.userId],
references: [admins.adminId], references: [users.id],
}), }),
applications: many(applications), applications: many(applications),
})); }));
@@ -45,7 +46,7 @@ const createSchema = createInsertSchema(registry, {
username: z.string().min(1), username: z.string().min(1),
password: z.string().min(1), password: z.string().min(1),
registryUrl: z.string(), registryUrl: z.string(),
adminId: z.string().min(1), userId: z.string().min(1),
registryId: z.string().min(1), registryId: z.string().min(1),
registryType: z.enum(["cloud"]), registryType: z.enum(["cloud"]),
imagePrefix: z.string().nullable().optional(), imagePrefix: z.string().nullable().optional(),

View File

@@ -23,6 +23,7 @@ import { postgres } from "./postgres";
import { redis } from "./redis"; import { redis } from "./redis";
import { sshKeys } from "./ssh-key"; import { sshKeys } from "./ssh-key";
import { generateAppName } from "./utils"; import { generateAppName } from "./utils";
import { users } from "./user";
export const serverStatus = pgEnum("serverStatus", ["active", "inactive"]); export const serverStatus = pgEnum("serverStatus", ["active", "inactive"]);
@@ -43,9 +44,9 @@ export const server = pgTable("server", {
createdAt: text("createdAt") createdAt: text("createdAt")
.notNull() .notNull()
.$defaultFn(() => new Date().toISOString()), .$defaultFn(() => new Date().toISOString()),
adminId: text("adminId") userId: text("userId")
.notNull() .notNull()
.references(() => admins.adminId, { onDelete: "cascade" }), .references(() => users.id, { onDelete: "cascade" }),
serverStatus: serverStatus("serverStatus").notNull().default("active"), serverStatus: serverStatus("serverStatus").notNull().default("active"),
command: text("command").notNull().default(""), command: text("command").notNull().default(""),
sshKeyId: text("sshKeyId").references(() => sshKeys.sshKeyId, { sshKeyId: text("sshKeyId").references(() => sshKeys.sshKeyId, {
@@ -100,9 +101,9 @@ export const server = pgTable("server", {
}); });
export const serverRelations = relations(server, ({ one, many }) => ({ export const serverRelations = relations(server, ({ one, many }) => ({
admin: one(admins, { user: one(users, {
fields: [server.adminId], fields: [server.userId],
references: [admins.adminId], references: [users.id],
}), }),
deployments: many(deployments), deployments: many(deployments),
sshKey: one(sshKeys, { sshKey: one(sshKeys, {

View File

@@ -1,13 +1,17 @@
import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { auth } from "./auth"; import { users } from "./user";
// OLD TABLE
export const sessionTable = pgTable("session", { export const sessionTable = pgTable("session", {
id: text("id").primaryKey(), id: text("id").primaryKey(),
expiresAt: timestamp("expires_at").notNull(),
token: text("token").notNull().unique(),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
userId: text("user_id") userId: text("user_id")
.notNull() .notNull()
.references(() => auth.id, { onDelete: "cascade" }), .references(() => users.id),
expiresAt: timestamp("expires_at", { impersonatedBy: text("impersonated_by"),
withTimezone: true,
mode: "date",
}).notNull(),
}); });

View File

@@ -7,6 +7,7 @@ import { admins } from "./admin";
import { applications } from "./application"; import { applications } from "./application";
import { compose } from "./compose"; import { compose } from "./compose";
import { server } from "./server"; import { server } from "./server";
import { users } from "./user";
export const sshKeys = pgTable("ssh-key", { export const sshKeys = pgTable("ssh-key", {
sshKeyId: text("sshKeyId") sshKeyId: text("sshKeyId")
@@ -21,7 +22,7 @@ export const sshKeys = pgTable("ssh-key", {
.notNull() .notNull()
.$defaultFn(() => new Date().toISOString()), .$defaultFn(() => new Date().toISOString()),
lastUsedAt: text("lastUsedAt"), lastUsedAt: text("lastUsedAt"),
adminId: text("adminId").references(() => admins.adminId, { userId: text("userId").references(() => users.id, {
onDelete: "cascade", onDelete: "cascade",
}), }),
}); });
@@ -30,9 +31,9 @@ export const sshKeysRelations = relations(sshKeys, ({ many, one }) => ({
applications: many(applications), applications: many(applications),
compose: many(compose), compose: many(compose),
servers: many(server), servers: many(server),
admin: one(admins, { user: one(users, {
fields: [sshKeys.adminId], fields: [sshKeys.userId],
references: [admins.adminId], references: [users.id],
}), }),
})); }));
@@ -48,7 +49,7 @@ export const apiCreateSshKey = createSchema
description: true, description: true,
privateKey: true, privateKey: true,
publicKey: true, publicKey: true,
adminId: true, userId: true,
}) })
.merge(sshKeyCreate.pick({ privateKey: true })); .merge(sshKeyCreate.pick({ privateKey: true }));

View File

@@ -1,10 +1,11 @@
import { relations, sql } from "drizzle-orm"; import { relations, sql } from "drizzle-orm";
import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core"; import { boolean, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { admins } from "./admin"; import { admins } from "./admin";
import { auth } from "./auth"; import { auth } from "./auth";
import { certificateType } from "./shared";
/** /**
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
* database instance for multiple projects. * database instance for multiple projects.
@@ -12,12 +13,13 @@ import { auth } from "./auth";
* @see https://orm.drizzle.team/docs/goodies#multi-project-schema * @see https://orm.drizzle.team/docs/goodies#multi-project-schema
*/ */
// OLD TABLE
export const users = pgTable("user", { export const users = pgTable("user", {
userId: text("userId") id: text("id")
.notNull() .notNull()
.primaryKey() .primaryKey()
.$defaultFn(() => nanoid()), .$defaultFn(() => nanoid()),
name: text("name").notNull().default(""),
token: text("token").notNull(), token: text("token").notNull(),
isRegistered: boolean("isRegistered").notNull().default(false), isRegistered: boolean("isRegistered").notNull().default(false),
expirationDate: timestamp("expirationDate", { expirationDate: timestamp("expirationDate", {
@@ -48,31 +50,102 @@ export const users = pgTable("user", {
.array() .array()
.notNull() .notNull()
.default(sql`ARRAY[]::text[]`), .default(sql`ARRAY[]::text[]`),
adminId: text("adminId") // authId: text("authId")
// .notNull()
// .references(() => auth.id, { onDelete: "cascade" }),
// Auth
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified").notNull(),
image: text("image"),
role: text("role"),
banned: boolean("banned"),
banReason: text("ban_reason"),
banExpires: timestamp("ban_expires"),
updatedAt: timestamp("updated_at").notNull(),
// Admin
serverIp: text("serverIp"),
certificateType: certificateType("certificateType").notNull().default("none"),
host: text("host"),
letsEncryptEmail: text("letsEncryptEmail"),
sshPrivateKey: text("sshPrivateKey"),
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
enableLogRotation: boolean("enableLogRotation").notNull().default(false),
// Metrics
enablePaidFeatures: boolean("enablePaidFeatures").notNull().default(false),
metricsConfig: jsonb("metricsConfig")
.$type<{
server: {
type: "Dokploy" | "Remote";
refreshRate: number;
port: number;
token: string;
urlCallback: string;
retentionDays: number;
cronJob: string;
thresholds: {
cpu: number;
memory: number;
};
};
containers: {
refreshRate: number;
services: {
include: string[];
exclude: string[];
};
};
}>()
.notNull() .notNull()
.references(() => admins.adminId, { onDelete: "cascade" }), .default({
authId: text("authId") server: {
type: "Dokploy",
refreshRate: 60,
port: 4500,
token: "",
retentionDays: 2,
cronJob: "",
urlCallback: "",
thresholds: {
cpu: 0,
memory: 0,
},
},
containers: {
refreshRate: 60,
services: {
include: [],
exclude: [],
},
},
}),
cleanupCacheApplications: boolean("cleanupCacheApplications")
.notNull() .notNull()
.references(() => auth.id, { onDelete: "cascade" }), .default(false),
cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews")
.notNull()
.default(false),
cleanupCacheOnCompose: boolean("cleanupCacheOnCompose")
.notNull()
.default(false),
}); });
export const usersRelations = relations(users, ({ one }) => ({ export const usersRelations = relations(users, ({ one }) => ({
auth: one(auth, { // auth: one(auth, {
fields: [users.authId], // fields: [users.authId],
references: [auth.id], // references: [auth.id],
}), // }),
admin: one(admins, { // admin: one(admins, {
fields: [users.adminId], // fields: [users.adminId],
references: [admins.adminId], // references: [admins.adminId],
}), // }),
})); }));
const createSchema = createInsertSchema(users, { const createSchema = createInsertSchema(users, {
userId: z.string().min(1), id: z.string().min(1),
authId: z.string().min(1), // authId: z.string().min(1),
token: z.string().min(1), token: z.string().min(1),
isRegistered: z.boolean().optional(), isRegistered: z.boolean().optional(),
adminId: z.string(), // adminId: z.string(),
accessedProjects: z.array(z.string()).optional(), accessedProjects: z.array(z.string()).optional(),
accessedServices: z.array(z.string()).optional(), accessedServices: z.array(z.string()).optional(),
canCreateProjects: z.boolean().optional(), canCreateProjects: z.boolean().optional(),
@@ -89,7 +162,7 @@ export const apiCreateUserInvitation = createSchema.pick({}).extend({
export const apiRemoveUser = createSchema export const apiRemoveUser = createSchema
.pick({ .pick({
authId: true, // authId: true,
}) })
.required(); .required();
@@ -101,7 +174,7 @@ export const apiFindOneToken = createSchema
export const apiAssignPermissions = createSchema export const apiAssignPermissions = createSchema
.pick({ .pick({
userId: true, id: true,
canCreateProjects: true, canCreateProjects: true,
canCreateServices: true, canCreateServices: true,
canDeleteProjects: true, canDeleteProjects: true,
@@ -118,12 +191,12 @@ export const apiAssignPermissions = createSchema
export const apiFindOneUser = createSchema export const apiFindOneUser = createSchema
.pick({ .pick({
userId: true, id: true,
}) })
.required(); .required();
export const apiFindOneUserByAuth = createSchema export const apiFindOneUserByAuth = createSchema
.pick({ .pick({
authId: true, // authId: true,
}) })
.required(); .required();

View File

@@ -118,3 +118,5 @@ export * from "./monitoring/utils";
export * from "./db/validations/domain"; export * from "./db/validations/domain";
export * from "./db/validations/index"; export * from "./db/validations/index";
export * from "./utils/gpu-setup"; export * from "./utils/gpu-setup";
export * from "./lib/auth";

View File

@@ -0,0 +1,14 @@
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { admin } from "better-auth/plugins";
import { db } from "../db";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
}),
emailAndPassword: {
enabled: true,
},
plugins: [admin()],
});

View File

@@ -94,7 +94,9 @@ export const updateAdminById = async (
}; };
export const isAdminPresent = async () => { export const isAdminPresent = async () => {
const admin = await db.query.admins.findFirst(); const admin = await db.query.users.findFirst({
where: eq(users.role, "admin"),
});
if (!admin) { if (!admin) {
return false; return false;
} }

View File

@@ -23,27 +23,27 @@ class LogRotationManager {
} }
private async initialize(): Promise<void> { private async initialize(): Promise<void> {
const isActive = await this.getStateFromDB(); // const isActive = await this.getStateFromDB();
if (isActive) { // if (isActive) {
await this.activateStream(); // await this.activateStream();
} // }
} }
private async getStateFromDB(): Promise<boolean> { // private async getStateFromDB(): Promise<boolean> {
const setting = await db.query.admins.findFirst({}); // const setting = await db.query.admins.findFirst({});
return setting?.enableLogRotation ?? false; // return setting?.enableLogRotation ?? false;
} // }
private async setStateInDB(active: boolean): Promise<void> { // private async setStateInDB(active: boolean): Promise<void> {
const admin = await db.query.admins.findFirst({}); // const admin = await db.query.admins.findFirst({});
if (!admin) { // if (!admin) {
return; // return;
} // }
await updateAdmin(admin?.authId, { // await updateAdmin(admin?.authId, {
enableLogRotation: active, // enableLogRotation: active,
}); // });
} // }
private async activateStream(): Promise<void> { private async activateStream(): Promise<void> {
const { DYNAMIC_TRAEFIK_PATH } = paths(); const { DYNAMIC_TRAEFIK_PATH } = paths();
@@ -76,26 +76,26 @@ class LogRotationManager {
} }
public async activate(): Promise<boolean> { public async activate(): Promise<boolean> {
const currentState = await this.getStateFromDB(); // const currentState = await this.getStateFromDB();
if (currentState) { // if (currentState) {
return true; // return true;
} // }
await this.setStateInDB(true); // await this.setStateInDB(true);
await this.activateStream(); // await this.activateStream();
return true; return true;
} }
public async deactivate(): Promise<boolean> { public async deactivate(): Promise<boolean> {
console.log("Deactivating log rotation..."); console.log("Deactivating log rotation...");
const currentState = await this.getStateFromDB(); // const currentState = await this.getStateFromDB();
if (!currentState) { // if (!currentState) {
console.log("Log rotation is already inactive in DB"); // console.log("Log rotation is already inactive in DB");
return true; // return true;
} // }
await this.setStateInDB(false); // await this.setStateInDB(false);
await this.deactivateStream(); // await this.deactivateStream();
console.log("Log rotation deactivated successfully"); console.log("Log rotation deactivated successfully");
return true; return true;
} }
@@ -115,8 +115,9 @@ class LogRotationManager {
} }
} }
public async getStatus(): Promise<boolean> { public async getStatus(): Promise<boolean> {
const dbState = await this.getStateFromDB(); // const dbState = await this.getStateFromDB();
return dbState; // return dbState;
return false;
} }
} }
export const logRotationManager = LogRotationManager.getInstance(); export const logRotationManager = LogRotationManager.getInstance();

206
pnpm-lock.yaml generated
View File

@@ -238,6 +238,9 @@ importers:
bcrypt: bcrypt:
specifier: 5.1.1 specifier: 5.1.1
version: 5.1.1(encoding@0.1.13) version: 5.1.1(encoding@0.1.13)
better-auth:
specifier: 1.1.16
version: 1.1.16
bl: bl:
specifier: 6.0.11 specifier: 6.0.11
version: 6.0.11 version: 6.0.11
@@ -273,10 +276,10 @@ importers:
version: 16.4.5 version: 16.4.5
drizzle-orm: drizzle-orm:
specifier: ^0.39.1 specifier: ^0.39.1
version: 0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7) version: 0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
drizzle-zod: drizzle-zod:
specifier: 0.5.1 specifier: 0.5.1
version: 0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8) version: 0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8)
fancy-ansi: fancy-ansi:
specifier: ^0.1.3 specifier: ^0.1.3
version: 0.1.3 version: 0.1.3
@@ -505,7 +508,7 @@ importers:
version: 16.4.5 version: 16.4.5
drizzle-orm: drizzle-orm:
specifier: ^0.39.1 specifier: ^0.39.1
version: 0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7) version: 0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
hono: hono:
specifier: ^4.5.8 specifier: ^4.5.8
version: 4.5.8 version: 4.5.8
@@ -567,6 +570,9 @@ importers:
bcrypt: bcrypt:
specifier: 5.1.1 specifier: 5.1.1
version: 5.1.1(encoding@0.1.13) version: 5.1.1(encoding@0.1.13)
better-auth:
specifier: 1.1.16
version: 1.1.16
bl: bl:
specifier: 6.0.11 specifier: 6.0.11
version: 6.0.11 version: 6.0.11
@@ -584,10 +590,10 @@ importers:
version: 16.4.5 version: 16.4.5
drizzle-orm: drizzle-orm:
specifier: ^0.39.1 specifier: ^0.39.1
version: 0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7) version: 0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
drizzle-zod: drizzle-zod:
specifier: 0.5.1 specifier: 0.5.1
version: 0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8) version: 0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8)
hi-base32: hi-base32:
specifier: ^0.5.1 specifier: ^0.5.1
version: 0.5.1 version: 0.5.1
@@ -748,6 +754,12 @@ packages:
'@balena/dockerignore@1.0.2': '@balena/dockerignore@1.0.2':
resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==}
'@better-auth/utils@0.2.3':
resolution: {integrity: sha512-Ap1GaSmo6JYhJhxJOpUB0HobkKPTNzfta+bLV89HfpyCAHN7p8ntCrmNFHNAVD0F6v0mywFVEUg1FUhNCc81Rw==}
'@better-fetch/fetch@1.1.12':
resolution: {integrity: sha512-B3bfloI/2UBQWIATRN6qmlORrvx3Mp0kkNjmXLv0b+DtbtR+pP4/I5kQA/rDUv+OReLywCCldf6co4LdDmh8JA==}
'@biomejs/biome@1.9.4': '@biomejs/biome@1.9.4':
resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
engines: {node: '>=14.21.3'} engines: {node: '>=14.21.3'}
@@ -1518,6 +1530,9 @@ packages:
'@hapi/bourne@3.0.0': '@hapi/bourne@3.0.0':
resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==}
'@hexagon/base64@1.1.28':
resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
'@hono/node-server@1.12.1': '@hono/node-server@1.12.1':
resolution: {integrity: sha512-C9l+08O8xtXB7Ppmy8DjBFH1hYji7JKzsU32Yt1poIIbdPp6S7aOI8IldDHD9YFJ55lv2c21ovNrmxatlHfhAg==} resolution: {integrity: sha512-C9l+08O8xtXB7Ppmy8DjBFH1hYji7JKzsU32Yt1poIIbdPp6S7aOI8IldDHD9YFJ55lv2c21ovNrmxatlHfhAg==}
engines: {node: '>=18.14.1'} engines: {node: '>=18.14.1'}
@@ -1697,6 +1712,9 @@ packages:
'@leichtgewicht/ip-codec@2.0.5': '@leichtgewicht/ip-codec@2.0.5':
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
'@levischuck/tiny-cbor@0.2.11':
resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==}
'@lezer/common@1.2.1': '@lezer/common@1.2.1':
resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==} resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==}
@@ -1807,10 +1825,17 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@noble/ciphers@0.6.0':
resolution: {integrity: sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==}
'@noble/hashes@1.5.0': '@noble/hashes@1.5.0':
resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==}
engines: {node: ^14.21.3 || >=16} engines: {node: ^14.21.3 || >=16}
'@noble/hashes@1.7.1':
resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==}
engines: {node: ^14.21.3 || >=16}
'@node-rs/argon2-android-arm-eabi@1.7.0': '@node-rs/argon2-android-arm-eabi@1.7.0':
resolution: {integrity: sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==} resolution: {integrity: sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
@@ -2136,6 +2161,21 @@ packages:
'@one-ini/wasm@0.1.1': '@one-ini/wasm@0.1.1':
resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
'@peculiar/asn1-android@2.3.15':
resolution: {integrity: sha512-8U2TIj59cRlSXTX2d0mzUKP7whfWGFMzTeC3qPgAbccXFrPNZLaDhpNEdG5U2QZ/tBv/IHlCJ8s+KYXpJeop6w==}
'@peculiar/asn1-ecc@2.3.15':
resolution: {integrity: sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==}
'@peculiar/asn1-rsa@2.3.15':
resolution: {integrity: sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==}
'@peculiar/asn1-schema@2.3.15':
resolution: {integrity: sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==}
'@peculiar/asn1-x509@2.3.15':
resolution: {integrity: sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==}
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'} engines: {node: '>=14'}
@@ -3147,6 +3187,13 @@ packages:
'@selderee/plugin-htmlparser2@0.11.0': '@selderee/plugin-htmlparser2@0.11.0':
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
'@simplewebauthn/browser@13.1.0':
resolution: {integrity: sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==}
'@simplewebauthn/server@13.1.1':
resolution: {integrity: sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==}
engines: {node: '>=20.0.0'}
'@sinclair/typebox@0.27.8': '@sinclair/typebox@0.27.8':
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
@@ -3762,6 +3809,10 @@ packages:
asn1@0.2.6: asn1@0.2.6:
resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==}
asn1js@3.0.5:
resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==}
engines: {node: '>=12.0.0'}
assertion-error@1.1.0: assertion-error@1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
@@ -3804,6 +3855,12 @@ packages:
before-after-hook@2.2.3: before-after-hook@2.2.3:
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
better-auth@1.1.16:
resolution: {integrity: sha512-Xc5pxafKZw4QVU8WYfkV2z4Hd8KCXXbphrgOpe2gA/EfanysLBhE1G/F7cEi5e0bW2pGR+vw6gf0ARHA7VFihg==}
better-call@0.3.3:
resolution: {integrity: sha512-N4lDVm0NGmFfDJ0XMQ4O83Zm/3dPlvIQdxvwvgSLSkjFX5PM4GUYSVAuxNzXN27QZMHDkrJTWUqxBrm4tPC3eA==}
binary-extensions@2.3.0: binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -5120,6 +5177,9 @@ packages:
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
hasBin: true hasBin: true
jose@5.9.6:
resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==}
joycon@3.1.1: joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -5223,6 +5283,10 @@ packages:
keyv@4.5.4: keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
kysely@0.27.5:
resolution: {integrity: sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==}
engines: {node: '>=14.0.0'}
leac@0.6.0: leac@0.6.0:
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
@@ -5608,6 +5672,10 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
nanostores@0.11.3:
resolution: {integrity: sha512-TUes3xKIX33re4QzdxwZ6tdbodjmn3tWXCEc1uokiEmo14sI1EaGYNs2k3bU2pyyGNmBqFGAVl6jAGWd06AVIg==}
engines: {node: ^18.0.0 || >=20.0.0}
napi-build-utils@1.0.2: napi-build-utils@1.0.2:
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
@@ -6058,6 +6126,13 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
pvtsutils@1.3.6:
resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==}
pvutils@1.1.3:
resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
engines: {node: '>=6.0.0'}
qrcode@1.5.4: qrcode@1.5.4:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
@@ -6403,6 +6478,9 @@ packages:
resolution: {integrity: sha512-cfmm3tqdnbuYw2FBmRTPBDaohYEbMJ3211T35o6eZdr4d7v69+ZeK1Av84Br7FLj2dlzyeZSbN6qTuXXE6dawQ==} resolution: {integrity: sha512-cfmm3tqdnbuYw2FBmRTPBDaohYEbMJ3211T35o6eZdr4d7v69+ZeK1Av84Br7FLj2dlzyeZSbN6qTuXXE6dawQ==}
engines: {node: '>=14.0'} engines: {node: '>=14.0'}
rou3@0.5.1:
resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==}
run-parallel@1.2.0: run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -6872,6 +6950,9 @@ packages:
tslib@2.6.3: tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tsx@4.16.2: tsx@4.16.2:
resolution: {integrity: sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==} resolution: {integrity: sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
@@ -7237,6 +7318,9 @@ packages:
zod@3.23.8: zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
zod@3.24.1:
resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
snapshots: snapshots:
'@alloc/quick-lru@5.2.0': {} '@alloc/quick-lru@5.2.0': {}
@@ -7266,6 +7350,12 @@ snapshots:
'@balena/dockerignore@1.0.2': {} '@balena/dockerignore@1.0.2': {}
'@better-auth/utils@0.2.3':
dependencies:
uncrypto: 0.1.3
'@better-fetch/fetch@1.1.12': {}
'@biomejs/biome@1.9.4': '@biomejs/biome@1.9.4':
optionalDependencies: optionalDependencies:
'@biomejs/cli-darwin-arm64': 1.9.4 '@biomejs/cli-darwin-arm64': 1.9.4
@@ -7832,6 +7922,8 @@ snapshots:
'@hapi/bourne@3.0.0': {} '@hapi/bourne@3.0.0': {}
'@hexagon/base64@1.1.28': {}
'@hono/node-server@1.12.1': {} '@hono/node-server@1.12.1': {}
'@hono/zod-validator@0.3.0(hono@4.5.8)(zod@3.23.8)': '@hono/zod-validator@0.3.0(hono@4.5.8)(zod@3.23.8)':
@@ -7981,6 +8073,8 @@ snapshots:
'@leichtgewicht/ip-codec@2.0.5': {} '@leichtgewicht/ip-codec@2.0.5': {}
'@levischuck/tiny-cbor@0.2.11': {}
'@lezer/common@1.2.1': {} '@lezer/common@1.2.1': {}
'@lezer/highlight@1.2.0': '@lezer/highlight@1.2.0':
@@ -8071,8 +8165,12 @@ snapshots:
'@next/swc-win32-x64-msvc@15.0.1': '@next/swc-win32-x64-msvc@15.0.1':
optional: true optional: true
'@noble/ciphers@0.6.0': {}
'@noble/hashes@1.5.0': {} '@noble/hashes@1.5.0': {}
'@noble/hashes@1.7.1': {}
'@node-rs/argon2-android-arm-eabi@1.7.0': '@node-rs/argon2-android-arm-eabi@1.7.0':
optional: true optional: true
@@ -8401,6 +8499,39 @@ snapshots:
'@one-ini/wasm@0.1.1': {} '@one-ini/wasm@0.1.1': {}
'@peculiar/asn1-android@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
asn1js: 3.0.5
tslib: 2.8.1
'@peculiar/asn1-ecc@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
asn1js: 3.0.5
tslib: 2.8.1
'@peculiar/asn1-rsa@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
asn1js: 3.0.5
tslib: 2.8.1
'@peculiar/asn1-schema@2.3.15':
dependencies:
asn1js: 3.0.5
pvtsutils: 1.3.6
tslib: 2.8.1
'@peculiar/asn1-x509@2.3.15':
dependencies:
'@peculiar/asn1-schema': 2.3.15
asn1js: 3.0.5
pvtsutils: 1.3.6
tslib: 2.8.1
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
optional: true optional: true
@@ -9394,6 +9525,18 @@ snapshots:
domhandler: 5.0.3 domhandler: 5.0.3
selderee: 0.11.0 selderee: 0.11.0
'@simplewebauthn/browser@13.1.0': {}
'@simplewebauthn/server@13.1.1':
dependencies:
'@hexagon/base64': 1.1.28
'@levischuck/tiny-cbor': 0.2.11
'@peculiar/asn1-android': 2.3.15
'@peculiar/asn1-ecc': 2.3.15
'@peculiar/asn1-rsa': 2.3.15
'@peculiar/asn1-schema': 2.3.15
'@peculiar/asn1-x509': 2.3.15
'@sinclair/typebox@0.27.8': {} '@sinclair/typebox@0.27.8': {}
'@sindresorhus/is@5.6.0': {} '@sindresorhus/is@5.6.0': {}
@@ -10304,6 +10447,12 @@ snapshots:
dependencies: dependencies:
safer-buffer: 2.1.2 safer-buffer: 2.1.2
asn1js@3.0.5:
dependencies:
pvtsutils: 1.3.6
pvutils: 1.1.3
tslib: 2.8.1
assertion-error@1.1.0: {} assertion-error@1.1.0: {}
async-await-queue@2.1.4: {} async-await-queue@2.1.4: {}
@@ -10352,6 +10501,28 @@ snapshots:
before-after-hook@2.2.3: {} before-after-hook@2.2.3: {}
better-auth@1.1.16:
dependencies:
'@better-auth/utils': 0.2.3
'@better-fetch/fetch': 1.1.12
'@noble/ciphers': 0.6.0
'@noble/hashes': 1.7.1
'@simplewebauthn/browser': 13.1.0
'@simplewebauthn/server': 13.1.1
better-call: 0.3.3
defu: 6.1.4
jose: 5.9.6
kysely: 0.27.5
nanostores: 0.11.3
zod: 3.24.1
better-call@0.3.3:
dependencies:
'@better-fetch/fetch': 1.1.12
rou3: 0.5.1
uncrypto: 0.1.3
zod: 3.24.1
binary-extensions@2.3.0: {} binary-extensions@2.3.0: {}
bindings@1.5.0: bindings@1.5.0:
@@ -10957,16 +11128,17 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
drizzle-orm@0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7): drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7):
optionalDependencies: optionalDependencies:
'@types/react': 18.3.5 '@types/react': 18.3.5
kysely: 0.27.5
postgres: 3.4.4 postgres: 3.4.4
react: 18.2.0 react: 18.2.0
sqlite3: 5.1.7 sqlite3: 5.1.7
drizzle-zod@0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8): drizzle-zod@0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8):
dependencies: dependencies:
drizzle-orm: 0.39.1(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7) drizzle-orm: 0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
zod: 3.23.8 zod: 3.23.8
eastasianwidth@0.2.0: {} eastasianwidth@0.2.0: {}
@@ -11686,6 +11858,8 @@ snapshots:
jiti@1.21.6: {} jiti@1.21.6: {}
jose@5.9.6: {}
joycon@3.1.1: {} joycon@3.1.1: {}
js-base64@3.7.7: {} js-base64@3.7.7: {}
@@ -11843,6 +12017,8 @@ snapshots:
dependencies: dependencies:
json-buffer: 3.0.1 json-buffer: 3.0.1
kysely@0.27.5: {}
leac@0.6.0: {} leac@0.6.0: {}
lefthook-darwin-arm64@1.8.4: lefthook-darwin-arm64@1.8.4:
@@ -12215,6 +12391,8 @@ snapshots:
nanoid@3.3.7: {} nanoid@3.3.7: {}
nanostores@0.11.3: {}
napi-build-utils@1.0.2: napi-build-utils@1.0.2:
optional: true optional: true
@@ -12692,6 +12870,12 @@ snapshots:
punycode@2.3.1: {} punycode@2.3.1: {}
pvtsutils@1.3.6:
dependencies:
tslib: 2.8.1
pvutils@1.1.3: {}
qrcode@1.5.4: qrcode@1.5.4:
dependencies: dependencies:
dijkstrajs: 1.0.3 dijkstrajs: 1.0.3
@@ -13054,6 +13238,8 @@ snapshots:
rotating-file-stream@3.2.3: {} rotating-file-stream@3.2.3: {}
rou3@0.5.1: {}
run-parallel@1.2.0: run-parallel@1.2.0:
dependencies: dependencies:
queue-microtask: 1.2.3 queue-microtask: 1.2.3
@@ -13617,6 +13803,8 @@ snapshots:
tslib@2.6.3: {} tslib@2.6.3: {}
tslib@2.8.1: {}
tsx@4.16.2: tsx@4.16.2:
dependencies: dependencies:
esbuild: 0.21.5 esbuild: 0.21.5
@@ -13994,3 +14182,5 @@ snapshots:
zod: 3.23.8 zod: 3.23.8
zod@3.23.8: {} zod@3.23.8: {}
zod@3.24.1: {}