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

View File

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

View File

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

View File

@@ -34,14 +34,14 @@ void app.prepare().then(async () => {
});
// WEBSOCKET
setupDrawerLogsWebSocketServer(server);
setupDeploymentLogsWebSocketServer(server);
setupDockerContainerLogsWebSocketServer(server);
setupDockerContainerTerminalWebSocketServer(server);
setupTerminalWebSocketServer(server);
if (!IS_CLOUD) {
setupDockerStatsMonitoringSocketServer(server);
}
// setupDrawerLogsWebSocketServer(server);
// setupDeploymentLogsWebSocketServer(server);
// setupDockerContainerLogsWebSocketServer(server);
// setupDockerContainerTerminalWebSocketServer(server);
// setupTerminalWebSocketServer(server);
// if (!IS_CLOUD) {
// setupDockerStatsMonitoringSocketServer(server);
// }
if (process.env.NODE_ENV === "production" && !IS_CLOUD) {
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"
},
"dependencies": {
"better-auth":"1.1.16",
"rotating-file-stream": "3.2.3",
"@faker-js/faker": "^8.4.1",
"@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";
export const admins = pgTable("admin", {
adminId: text("adminId")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()),
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),
authId: text("authId")
.notNull()
.references(() => auth.id, { onDelete: "cascade" }),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
stripeCustomerId: text("stripeCustomerId"),
stripeSubscriptionId: text("stripeSubscriptionId"),
serversQuantity: integer("serversQuantity").notNull().default(0),
// 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()
.default({
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()
.default(false),
cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews")
.notNull()
.default(false),
cleanupCacheOnCompose: boolean("cleanupCacheOnCompose")
.notNull()
.default(false),
// adminId: text("adminId")
// .notNull()
// .primaryKey()
// .$defaultFn(() => nanoid()),
// 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),
// authId: text("authId")
// .notNull()
// .references(() => auth.id, { onDelete: "cascade" }),
// createdAt: text("createdAt")
// .notNull()
// .$defaultFn(() => new Date().toISOString()),
// stripeCustomerId: text("stripeCustomerId"),
// stripeSubscriptionId: text("stripeSubscriptionId"),
// serversQuantity: integer("serversQuantity").notNull().default(0),
// // 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()
// .default({
// 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()
// .default(false),
// cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews")
// .notNull()
// .default(false),
// cleanupCacheOnCompose: boolean("cleanupCacheOnCompose")
// .notNull()
// .default(false),
});
export const adminsRelations = relations(admins, ({ one, many }) => ({
auth: one(auth, {
fields: [admins.authId],
references: [auth.id],
}),
users: many(users),
registry: many(registry),
sshKeys: many(sshKeys),
certificates: many(certificates),
// auth: one(auth, {
// fields: [admins.authId],
// references: [auth.id],
// }),
// users: many(users),
// registry: many(registry),
// sshKeys: many(sshKeys),
// certificates: many(certificates),
}));
const createSchema = createInsertSchema(admins, {
adminId: z.string(),
enableDockerCleanup: z.boolean().optional(),
sshPrivateKey: z.string().optional(),
certificateType: z.enum(["letsencrypt", "none"]).default("none"),
serverIp: z.string().optional(),
letsEncryptEmail: z.string().optional(),
// adminId: z.string(),
// enableDockerCleanup: z.boolean().optional(),
// sshPrivateKey: z.string().optional(),
// certificateType: z.enum(["letsencrypt", "none"]).default("none"),
// serverIp: z.string().optional(),
// letsEncryptEmail: z.string().optional(),
});
export const apiUpdateAdmin = createSchema.partial();
export const apiSaveSSHKey = createSchema
.pick({
sshPrivateKey: true,
// sshPrivateKey: true,
})
.required();
export const apiAssignDomain = createSchema
.pick({
host: true,
certificateType: true,
letsEncryptEmail: true,
// host: true,
// certificateType: true,
// letsEncryptEmail: true,
})
.required()
.partial({
letsEncryptEmail: true,
// letsEncryptEmail: true,
});
export const apiUpdateDockerCleanup = createSchema
.pick({
enableDockerCleanup: true,
// enableDockerCleanup: true,
})
.required()
.extend({

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ import { nanoid } from "nanoid";
import { z } from "zod";
import { admins } from "./admin";
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
* database instance for multiple projects.
@@ -27,15 +28,15 @@ export const registry = pgTable("registry", {
.notNull()
.$defaultFn(() => new Date().toISOString()),
registryType: registryType("selfHosted").notNull().default("cloud"),
adminId: text("adminId")
userId: text("userId")
.notNull()
.references(() => admins.adminId, { onDelete: "cascade" }),
.references(() => users.id, { onDelete: "cascade" }),
});
export const registryRelations = relations(registry, ({ one, many }) => ({
admin: one(admins, {
fields: [registry.adminId],
references: [admins.adminId],
user: one(users, {
fields: [registry.userId],
references: [users.id],
}),
applications: many(applications),
}));
@@ -45,7 +46,7 @@ const createSchema = createInsertSchema(registry, {
username: z.string().min(1),
password: z.string().min(1),
registryUrl: z.string(),
adminId: z.string().min(1),
userId: z.string().min(1),
registryId: z.string().min(1),
registryType: z.enum(["cloud"]),
imagePrefix: z.string().nullable().optional(),

View File

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

View File

@@ -1,13 +1,17 @@
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { auth } from "./auth";
import { users } from "./user";
// OLD TABLE
export const sessionTable = 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(() => auth.id, { onDelete: "cascade" }),
expiresAt: timestamp("expires_at", {
withTimezone: true,
mode: "date",
}).notNull(),
.references(() => users.id),
impersonatedBy: text("impersonated_by"),
});

View File

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

View File

@@ -1,10 +1,11 @@
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 { nanoid } from "nanoid";
import { z } from "zod";
import { admins } from "./admin";
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
* database instance for multiple projects.
@@ -12,12 +13,13 @@ import { auth } from "./auth";
* @see https://orm.drizzle.team/docs/goodies#multi-project-schema
*/
// OLD TABLE
export const users = pgTable("user", {
userId: text("userId")
id: text("id")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()),
name: text("name").notNull().default(""),
token: text("token").notNull(),
isRegistered: boolean("isRegistered").notNull().default(false),
expirationDate: timestamp("expirationDate", {
@@ -48,31 +50,102 @@ export const users = pgTable("user", {
.array()
.notNull()
.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()
.references(() => admins.adminId, { onDelete: "cascade" }),
authId: text("authId")
.default({
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()
.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 }) => ({
auth: one(auth, {
fields: [users.authId],
references: [auth.id],
}),
admin: one(admins, {
fields: [users.adminId],
references: [admins.adminId],
}),
// auth: one(auth, {
// fields: [users.authId],
// references: [auth.id],
// }),
// admin: one(admins, {
// fields: [users.adminId],
// references: [admins.adminId],
// }),
}));
const createSchema = createInsertSchema(users, {
userId: z.string().min(1),
authId: z.string().min(1),
id: z.string().min(1),
// authId: z.string().min(1),
token: z.string().min(1),
isRegistered: z.boolean().optional(),
adminId: z.string(),
// adminId: z.string(),
accessedProjects: z.array(z.string()).optional(),
accessedServices: z.array(z.string()).optional(),
canCreateProjects: z.boolean().optional(),
@@ -89,7 +162,7 @@ export const apiCreateUserInvitation = createSchema.pick({}).extend({
export const apiRemoveUser = createSchema
.pick({
authId: true,
// authId: true,
})
.required();
@@ -101,7 +174,7 @@ export const apiFindOneToken = createSchema
export const apiAssignPermissions = createSchema
.pick({
userId: true,
id: true,
canCreateProjects: true,
canCreateServices: true,
canDeleteProjects: true,
@@ -118,12 +191,12 @@ export const apiAssignPermissions = createSchema
export const apiFindOneUser = createSchema
.pick({
userId: true,
id: true,
})
.required();
export const apiFindOneUserByAuth = createSchema
.pick({
authId: true,
// authId: true,
})
.required();

View File

@@ -118,3 +118,5 @@ export * from "./monitoring/utils";
export * from "./db/validations/domain";
export * from "./db/validations/index";
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 () => {
const admin = await db.query.admins.findFirst();
const admin = await db.query.users.findFirst({
where: eq(users.role, "admin"),
});
if (!admin) {
return false;
}

View File

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

206
pnpm-lock.yaml generated
View File

@@ -238,6 +238,9 @@ importers:
bcrypt:
specifier: 5.1.1
version: 5.1.1(encoding@0.1.13)
better-auth:
specifier: 1.1.16
version: 1.1.16
bl:
specifier: 6.0.11
version: 6.0.11
@@ -273,10 +276,10 @@ importers:
version: 16.4.5
drizzle-orm:
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:
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:
specifier: ^0.1.3
version: 0.1.3
@@ -505,7 +508,7 @@ importers:
version: 16.4.5
drizzle-orm:
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:
specifier: ^4.5.8
version: 4.5.8
@@ -567,6 +570,9 @@ importers:
bcrypt:
specifier: 5.1.1
version: 5.1.1(encoding@0.1.13)
better-auth:
specifier: 1.1.16
version: 1.1.16
bl:
specifier: 6.0.11
version: 6.0.11
@@ -584,10 +590,10 @@ importers:
version: 16.4.5
drizzle-orm:
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:
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:
specifier: ^0.5.1
version: 0.5.1
@@ -748,6 +754,12 @@ packages:
'@balena/dockerignore@1.0.2':
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':
resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
engines: {node: '>=14.21.3'}
@@ -1518,6 +1530,9 @@ packages:
'@hapi/bourne@3.0.0':
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':
resolution: {integrity: sha512-C9l+08O8xtXB7Ppmy8DjBFH1hYji7JKzsU32Yt1poIIbdPp6S7aOI8IldDHD9YFJ55lv2c21ovNrmxatlHfhAg==}
engines: {node: '>=18.14.1'}
@@ -1697,6 +1712,9 @@ packages:
'@leichtgewicht/ip-codec@2.0.5':
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
'@levischuck/tiny-cbor@0.2.11':
resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==}
'@lezer/common@1.2.1':
resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==}
@@ -1807,10 +1825,17 @@ packages:
cpu: [x64]
os: [win32]
'@noble/ciphers@0.6.0':
resolution: {integrity: sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==}
'@noble/hashes@1.5.0':
resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==}
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':
resolution: {integrity: sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==}
engines: {node: '>= 10'}
@@ -2136,6 +2161,21 @@ packages:
'@one-ini/wasm@0.1.1':
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':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@@ -3147,6 +3187,13 @@ packages:
'@selderee/plugin-htmlparser2@0.11.0':
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':
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
@@ -3762,6 +3809,10 @@ packages:
asn1@0.2.6:
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:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
@@ -3804,6 +3855,12 @@ packages:
before-after-hook@2.2.3:
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:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
@@ -5120,6 +5177,9 @@ packages:
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
hasBin: true
jose@5.9.6:
resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==}
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
@@ -5223,6 +5283,10 @@ packages:
keyv@4.5.4:
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:
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
@@ -5608,6 +5672,10 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
nanostores@0.11.3:
resolution: {integrity: sha512-TUes3xKIX33re4QzdxwZ6tdbodjmn3tWXCEc1uokiEmo14sI1EaGYNs2k3bU2pyyGNmBqFGAVl6jAGWd06AVIg==}
engines: {node: ^18.0.0 || >=20.0.0}
napi-build-utils@1.0.2:
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
@@ -6058,6 +6126,13 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
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:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
engines: {node: '>=10.13.0'}
@@ -6403,6 +6478,9 @@ packages:
resolution: {integrity: sha512-cfmm3tqdnbuYw2FBmRTPBDaohYEbMJ3211T35o6eZdr4d7v69+ZeK1Av84Br7FLj2dlzyeZSbN6qTuXXE6dawQ==}
engines: {node: '>=14.0'}
rou3@0.5.1:
resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==}
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -6872,6 +6950,9 @@ packages:
tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tsx@4.16.2:
resolution: {integrity: sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==}
engines: {node: '>=18.0.0'}
@@ -7237,6 +7318,9 @@ packages:
zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
zod@3.24.1:
resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
snapshots:
'@alloc/quick-lru@5.2.0': {}
@@ -7266,6 +7350,12 @@ snapshots:
'@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':
optionalDependencies:
'@biomejs/cli-darwin-arm64': 1.9.4
@@ -7832,6 +7922,8 @@ snapshots:
'@hapi/bourne@3.0.0': {}
'@hexagon/base64@1.1.28': {}
'@hono/node-server@1.12.1': {}
'@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': {}
'@levischuck/tiny-cbor@0.2.11': {}
'@lezer/common@1.2.1': {}
'@lezer/highlight@1.2.0':
@@ -8071,8 +8165,12 @@ snapshots:
'@next/swc-win32-x64-msvc@15.0.1':
optional: true
'@noble/ciphers@0.6.0': {}
'@noble/hashes@1.5.0': {}
'@noble/hashes@1.7.1': {}
'@node-rs/argon2-android-arm-eabi@1.7.0':
optional: true
@@ -8401,6 +8499,39 @@ snapshots:
'@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':
optional: true
@@ -9394,6 +9525,18 @@ snapshots:
domhandler: 5.0.3
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': {}
'@sindresorhus/is@5.6.0': {}
@@ -10304,6 +10447,12 @@ snapshots:
dependencies:
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: {}
async-await-queue@2.1.4: {}
@@ -10352,6 +10501,28 @@ snapshots:
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: {}
bindings@1.5.0:
@@ -10957,16 +11128,17 @@ snapshots:
transitivePeerDependencies:
- 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:
'@types/react': 18.3.5
kysely: 0.27.5
postgres: 3.4.4
react: 18.2.0
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:
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
eastasianwidth@0.2.0: {}
@@ -11686,6 +11858,8 @@ snapshots:
jiti@1.21.6: {}
jose@5.9.6: {}
joycon@3.1.1: {}
js-base64@3.7.7: {}
@@ -11843,6 +12017,8 @@ snapshots:
dependencies:
json-buffer: 3.0.1
kysely@0.27.5: {}
leac@0.6.0: {}
lefthook-darwin-arm64@1.8.4:
@@ -12215,6 +12391,8 @@ snapshots:
nanoid@3.3.7: {}
nanostores@0.11.3: {}
napi-build-utils@1.0.2:
optional: true
@@ -12692,6 +12870,12 @@ snapshots:
punycode@2.3.1: {}
pvtsutils@1.3.6:
dependencies:
tslib: 2.8.1
pvutils@1.1.3: {}
qrcode@1.5.4:
dependencies:
dijkstrajs: 1.0.3
@@ -13054,6 +13238,8 @@ snapshots:
rotating-file-stream@3.2.3: {}
rou3@0.5.1: {}
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@@ -13617,6 +13803,8 @@ snapshots:
tslib@2.6.3: {}
tslib@2.8.1: {}
tsx@4.16.2:
dependencies:
esbuild: 0.21.5
@@ -13994,3 +14182,5 @@ snapshots:
zod: 3.23.8
zod@3.23.8: {}
zod@3.24.1: {}