mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-15 20:25:23 +02:00
- Introduced a new feature allowing users to enable or disable invoice email notifications in the billing settings. - Implemented email notifications for successful invoice payments and payment failures, enhancing user communication regarding billing. - Updated the database schema to include a new column for storing user preferences on invoice notifications. - Added corresponding email templates for invoice notifications and payment failure alerts. These changes improve user experience by keeping users informed about their billing status and actions required.
244 lines
6.5 KiB
TypeScript
244 lines
6.5 KiB
TypeScript
import { paths } from "@dokploy/server/constants";
|
|
import { relations, sql } from "drizzle-orm";
|
|
import {
|
|
boolean,
|
|
integer,
|
|
pgTable,
|
|
text,
|
|
timestamp,
|
|
} from "drizzle-orm/pg-core";
|
|
import { createInsertSchema } from "drizzle-zod";
|
|
import { nanoid } from "nanoid";
|
|
import { z } from "zod";
|
|
import { account, apikey, organization } from "./account";
|
|
import { backups } from "./backups";
|
|
import { projects } from "./project";
|
|
import { schedules } from "./schedule";
|
|
import { ssoProvider } from "./sso";
|
|
/**
|
|
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
|
|
* database instance for multiple projects.
|
|
*
|
|
* @see https://orm.drizzle.team/docs/goodies#multi-project-schema
|
|
*/
|
|
|
|
// OLD TABLE
|
|
|
|
// TEMP
|
|
export const user = pgTable("user", {
|
|
id: text("id")
|
|
.notNull()
|
|
.primaryKey()
|
|
.$defaultFn(() => nanoid()),
|
|
firstName: text("firstName").notNull().default(""),
|
|
lastName: text("lastName").notNull().default(""),
|
|
isRegistered: boolean("isRegistered").notNull().default(false),
|
|
expirationDate: text("expirationDate")
|
|
.notNull()
|
|
.$defaultFn(() => new Date().toISOString()),
|
|
createdAt2: text("createdAt")
|
|
.notNull()
|
|
.$defaultFn(() => new Date().toISOString()),
|
|
createdAt: timestamp("created_at").defaultNow(),
|
|
// Auth
|
|
twoFactorEnabled: boolean("two_factor_enabled"),
|
|
email: text("email").notNull().unique(),
|
|
emailVerified: boolean("email_verified").notNull(),
|
|
image: text("image"),
|
|
banned: boolean("banned"),
|
|
banReason: text("ban_reason"),
|
|
banExpires: timestamp("ban_expires"),
|
|
updatedAt: timestamp("updated_at").notNull(),
|
|
// Admin
|
|
role: text("role").notNull().default("user"),
|
|
// Metrics
|
|
enablePaidFeatures: boolean("enablePaidFeatures").notNull().default(false),
|
|
allowImpersonation: boolean("allowImpersonation").notNull().default(false),
|
|
// Enterprise / proprietary features
|
|
enableEnterpriseFeatures: boolean("enableEnterpriseFeatures")
|
|
.notNull()
|
|
.default(false),
|
|
licenseKey: text("licenseKey"),
|
|
isValidEnterpriseLicense: boolean("isValidEnterpriseLicense")
|
|
.notNull()
|
|
.default(false),
|
|
stripeCustomerId: text("stripeCustomerId"),
|
|
stripeSubscriptionId: text("stripeSubscriptionId"),
|
|
serversQuantity: integer("serversQuantity").notNull().default(0),
|
|
sendInvoiceNotifications: boolean("sendInvoiceNotifications")
|
|
.notNull()
|
|
.default(false),
|
|
isEnterpriseCloud: boolean("isEnterpriseCloud").notNull().default(false),
|
|
trustedOrigins: text("trustedOrigins").array(),
|
|
bookmarkedTemplates: text("bookmarkedTemplates")
|
|
.array()
|
|
.default(sql`ARRAY[]::text[]`),
|
|
});
|
|
|
|
export const usersRelations = relations(user, ({ one, many }) => ({
|
|
account: one(account, {
|
|
fields: [user.id],
|
|
references: [account.userId],
|
|
}),
|
|
organizations: many(organization),
|
|
projects: many(projects),
|
|
apiKeys: many(apikey),
|
|
ssoProviders: many(ssoProvider),
|
|
backups: many(backups),
|
|
schedules: many(schedules),
|
|
}));
|
|
|
|
const createSchema = createInsertSchema(user, {
|
|
id: z.string().min(1),
|
|
isRegistered: z.boolean().optional(),
|
|
}).omit({
|
|
role: true,
|
|
trustedOrigins: true,
|
|
bookmarkedTemplates: true,
|
|
isValidEnterpriseLicense: true,
|
|
isEnterpriseCloud: true,
|
|
});
|
|
|
|
export const apiCreateUserInvitation = createSchema.pick({}).extend({
|
|
email: z.string().email(),
|
|
});
|
|
|
|
export const apiRemoveUser = createSchema
|
|
.pick({
|
|
id: true,
|
|
})
|
|
.required();
|
|
|
|
export const apiFindOneToken = createSchema
|
|
.pick({})
|
|
.required()
|
|
.extend({
|
|
token: z.string().min(1),
|
|
});
|
|
|
|
export const apiAssignPermissions = createSchema
|
|
.pick({
|
|
id: true,
|
|
// canCreateProjects: true,
|
|
// canCreateServices: true,
|
|
// canDeleteProjects: true,
|
|
// canDeleteServices: true,
|
|
// accessedProjects: true,
|
|
// accessedServices: true,
|
|
// canAccessToTraefikFiles: true,
|
|
// canAccessToDocker: true,
|
|
// canAccessToAPI: true,
|
|
// canAccessToSSHKeys: true,
|
|
// canAccessToGitProviders: true,
|
|
})
|
|
.extend({
|
|
accessedProjects: z.array(z.string()).optional(),
|
|
accessedEnvironments: z.array(z.string()).optional(),
|
|
accessedServices: z.array(z.string()).optional(),
|
|
accessedGitProviders: z.array(z.string()).optional(),
|
|
accessedServers: z.array(z.string()).optional(),
|
|
canCreateProjects: z.boolean().optional(),
|
|
canCreateServices: z.boolean().optional(),
|
|
canDeleteProjects: z.boolean().optional(),
|
|
canDeleteServices: z.boolean().optional(),
|
|
canAccessToDocker: z.boolean().optional(),
|
|
canAccessToTraefikFiles: z.boolean().optional(),
|
|
canAccessToAPI: z.boolean().optional(),
|
|
canAccessToSSHKeys: z.boolean().optional(),
|
|
canAccessToGitProviders: z.boolean().optional(),
|
|
canDeleteEnvironments: z.boolean().optional(),
|
|
canCreateEnvironments: z.boolean().optional(),
|
|
})
|
|
.required();
|
|
|
|
export const apiFindOneUser = createSchema
|
|
.pick({
|
|
id: true,
|
|
})
|
|
.required();
|
|
|
|
export const apiFindOneUserByAuth = createSchema
|
|
.pick({
|
|
// authId: true,
|
|
})
|
|
.required();
|
|
|
|
export const apiTraefikConfig = z.object({
|
|
traefikConfig: z.string().min(1),
|
|
});
|
|
|
|
export const apiModifyTraefikConfig = z.object({
|
|
path: z.string().min(1),
|
|
traefikConfig: z.string().min(1),
|
|
serverId: z.string().optional(),
|
|
});
|
|
export const apiReadTraefikConfig = z.object({
|
|
path: z
|
|
.string()
|
|
.min(1)
|
|
.refine(
|
|
(path) => {
|
|
// Prevent directory traversal attacks
|
|
if (path.includes("../") || path.includes("..\\")) {
|
|
return false;
|
|
}
|
|
|
|
const { MAIN_TRAEFIK_PATH } = paths();
|
|
if (path.startsWith("/") && !path.startsWith(MAIN_TRAEFIK_PATH)) {
|
|
return false;
|
|
}
|
|
// Prevent null bytes and other dangerous characters
|
|
if (path.includes("\0") || path.includes("\x00")) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
{
|
|
message:
|
|
"Invalid path: path traversal or unauthorized directory access detected",
|
|
},
|
|
),
|
|
serverId: z.string().optional(),
|
|
});
|
|
|
|
export const apiEnableDashboard = z.object({
|
|
enableDashboard: z.boolean().optional(),
|
|
serverId: z.string().optional(),
|
|
});
|
|
|
|
export const apiServerSchema = z
|
|
.object({
|
|
serverId: z.string().optional(),
|
|
})
|
|
.optional();
|
|
|
|
export const apiReadStatsLogs = z.object({
|
|
page: z
|
|
.object({
|
|
pageIndex: z.number(),
|
|
pageSize: z.number(),
|
|
})
|
|
.optional(),
|
|
status: z.string().array().optional(),
|
|
search: z.string().optional(),
|
|
sort: z.object({ id: z.string(), desc: z.boolean() }).optional(),
|
|
dateRange: z
|
|
.object({
|
|
start: z.string().optional(),
|
|
end: z.string().optional(),
|
|
})
|
|
.optional(),
|
|
});
|
|
|
|
export const apiUpdateUser = createSchema.partial().extend({
|
|
email: z
|
|
.string()
|
|
.email("Please enter a valid email address")
|
|
.min(1, "Email is required")
|
|
.optional(),
|
|
password: z.string().optional(),
|
|
currentPassword: z.string().optional(),
|
|
firstName: z.string().optional(),
|
|
lastName: z.string().optional(),
|
|
});
|