mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Merge branch 'feature'
This commit is contained in:
@@ -16,6 +16,10 @@ SMTP_PORT=
|
|||||||
SMTP_USER=
|
SMTP_USER=
|
||||||
SMTP_PASSWORD=
|
SMTP_PASSWORD=
|
||||||
SMTP_FROM=
|
SMTP_FROM=
|
||||||
|
#SMTP_SECURE="true"
|
||||||
|
#Port 587 -> secure: false
|
||||||
|
#Port 465 -> secure: true
|
||||||
|
#By default : true
|
||||||
|
|
||||||
# Google
|
# Google
|
||||||
AUTH_GOOGLE_ID=
|
AUTH_GOOGLE_ID=
|
||||||
|
|||||||
+2
-2
@@ -26,5 +26,5 @@ keywords:
|
|||||||
- web-ui
|
- web-ui
|
||||||
- agent
|
- agent
|
||||||
license: Apache-2.0
|
license: Apache-2.0
|
||||||
version: 1.2.1-rc.1
|
version: 1.2.1-rc.3
|
||||||
date-released: "2026-01-18"
|
date-released: "2026-01-20"
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ export async function POST(
|
|||||||
const generatedId = formData.get("generatedId") as string | null;
|
const generatedId = formData.get("generatedId") as string | null;
|
||||||
const method = formData.get("method") as string | null;
|
const method = formData.get("method") as string | null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!generatedId || !isUuidv4(generatedId)) {
|
if (!generatedId || !isUuidv4(generatedId)) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{error: "generatedId is not a valid UUID"},
|
{error: "generatedId is not a valid UUID"},
|
||||||
@@ -113,6 +116,7 @@ export async function POST(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{error: "File is required for successful backup"},
|
{error: "File is required for successful backup"},
|
||||||
@@ -127,7 +131,11 @@ export async function POST(
|
|||||||
const fileName = `${uuid}${fileExtension}`;
|
const fileName = `${uuid}${fileExtension}`;
|
||||||
const buffer = Buffer.from(await decryptedFile.arrayBuffer());
|
const buffer = Buffer.from(await decryptedFile.arrayBuffer());
|
||||||
|
|
||||||
await storeBackupFiles(backup, database, buffer, fileName)
|
|
||||||
|
console.log(extension)
|
||||||
|
|
||||||
|
|
||||||
|
const storageResults = await storeBackupFiles(backup, database, buffer, fileName)
|
||||||
eventEmitter.emit('modification', {update: true});
|
eventEmitter.emit('modification', {update: true});
|
||||||
|
|
||||||
await sendNotificationsBackupRestore(database, "success_backup");
|
await sendNotificationsBackupRestore(database, "success_backup");
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {Database} from "@/db/schema/07_database";
|
|||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
import {db as dbClient} from "@/db";
|
import {db as dbClient} from "@/db";
|
||||||
import {and, eq, inArray} from "drizzle-orm";
|
import {and, eq, inArray} from "drizzle-orm";
|
||||||
import {EDbmsSchema} from "@/db/schema/types";
|
import {dbmsEnumSchema, EDbmsSchema} from "@/db/schema/types";
|
||||||
import {ServerActionResult} from "@/types/action-type";
|
import {ServerActionResult} from "@/types/action-type";
|
||||||
import {SafeActionResult} from "next-safe-action";
|
import {SafeActionResult} from "next-safe-action";
|
||||||
import {ZodString} from "zod";
|
import {ZodString} from "zod";
|
||||||
@@ -50,6 +50,12 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
|
|||||||
{status: 500}
|
{status: 500}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!dbmsEnumSchema.safeParse(db.dbms).success) {
|
||||||
|
console.log(`Database type not available: ${db.dbms}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const [databaseCreated] = await dbClient
|
const [databaseCreated] = await dbClient
|
||||||
.insert(drizzleDb.schemas.database)
|
.insert(drizzleDb.schemas.database)
|
||||||
.values({
|
.values({
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "portabase",
|
"name": "portabase",
|
||||||
"version": "1.2.1-rc.1",
|
"version": "1.2.1-rc.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack -p 8887",
|
"dev": "next dev --turbopack -p 8887",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -26,7 +26,7 @@ import {
|
|||||||
} from "@/components/wrappers/dashboard/admin/settings/email/email-form/email-form.action";
|
} from "@/components/wrappers/dashboard/admin/settings/email/email-form/email-form.action";
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
import {useRouter} from "next/navigation";
|
import {useRouter} from "next/navigation";
|
||||||
import {sendEmail} from "@/lib/email/email-helper";
|
import {sendEmail} from "@/lib/email";
|
||||||
import {render} from "@react-email/components";
|
import {render} from "@react-email/components";
|
||||||
import EmailSettingsTest from "@/components/emails/email-settings-test";
|
import EmailSettingsTest from "@/components/emails/email-settings-test";
|
||||||
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
|
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {userAction} from "@/lib/safe-actions/actions";
|
|||||||
import {
|
import {
|
||||||
addMemberOrganizationAction
|
addMemberOrganizationAction
|
||||||
} from "@/components/wrappers/dashboard/admin/organizations/organization/details/add-member.action";
|
} from "@/components/wrappers/dashboard/admin/organizations/organization/details/add-member.action";
|
||||||
import {sendEmail} from "@/lib/email/email-helper";
|
import {sendEmail} from "@/lib/email";
|
||||||
import EmailCreateUser from "@/components/emails/email-create-user";
|
import EmailCreateUser from "@/components/emails/email-create-user";
|
||||||
import {SignUpUser} from "@/types/auth";
|
import {SignUpUser} from "@/types/auth";
|
||||||
import {createUserDb} from "@/db/services/user";
|
import {createUserDb} from "@/db/services/user";
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TYPE "public"."dbms_status" ADD VALUE 'mongodb';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "settings" ADD COLUMN "smtp_secure" boolean;
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -211,6 +211,20 @@
|
|||||||
"when": 1768743644834,
|
"when": 1768743644834,
|
||||||
"tag": "0029_lowly_white_tiger",
|
"tag": "0029_lowly_white_tiger",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 30,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1768763883339,
|
||||||
|
"tag": "0030_dizzy_morlocks",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 31,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1768931603846,
|
||||||
|
"tag": "0031_chemical_edwin_jarvis",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import {pgTable, timestamp, uuid, varchar} from "drizzle-orm/pg-core";
|
import {boolean, pgTable, timestamp, uuid, varchar} from "drizzle-orm/pg-core";
|
||||||
import {typeStorageEnum} from "./types";
|
import {typeStorageEnum} from "./types";
|
||||||
import {createSelectSchema} from "drizzle-zod";
|
import {createSelectSchema} from "drizzle-zod";
|
||||||
import {z} from "zod";
|
import {z} from "zod";
|
||||||
@@ -19,6 +19,7 @@ export const setting = pgTable("settings", {
|
|||||||
smtpHost: varchar("smtp_host", {length: 255}),
|
smtpHost: varchar("smtp_host", {length: 255}),
|
||||||
smtpPort: varchar("smtp_port", {length: 255}),
|
smtpPort: varchar("smtp_port", {length: 255}),
|
||||||
smtpUser: varchar("smtp_user", {length: 255}),
|
smtpUser: varchar("smtp_user", {length: 255}),
|
||||||
|
smtpSecure: boolean("smtp_secure"),
|
||||||
defaultStorageChannelId: uuid('default_storage_channel_id')
|
defaultStorageChannelId: uuid('default_storage_channel_id')
|
||||||
.references(() => storageChannel.id, {onDelete: "set null"}),
|
.references(() => storageChannel.id, {onDelete: "set null"}),
|
||||||
...timestamps
|
...timestamps
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { pgEnum } from "drizzle-orm/pg-core";
|
|||||||
import {createSelectSchema} from "drizzle-zod";
|
import {createSelectSchema} from "drizzle-zod";
|
||||||
import {z} from "zod";
|
import {z} from "zod";
|
||||||
|
|
||||||
export const dbmsEnum = pgEnum("dbms_status", ["postgresql", "mysql"]);
|
export const dbmsEnum = pgEnum("dbms_status", ["postgresql", "mysql", "mongodb"]);
|
||||||
export const statusEnum = pgEnum("status", ["waiting", "ongoing", "failed", "success"]);
|
export const statusEnum = pgEnum("status", ["waiting", "ongoing", "failed", "success"]);
|
||||||
export const typeStorageEnum = pgEnum("type_storage", ["local", "s3"]);
|
export const typeStorageEnum = pgEnum("type_storage", ["local", "s3"]);
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export const env = createEnv({
|
|||||||
SMTP_HOST: z.string().optional(),
|
SMTP_HOST: z.string().optional(),
|
||||||
SMTP_PORT: z.string().optional(),
|
SMTP_PORT: z.string().optional(),
|
||||||
SMTP_USER: z.string().optional(),
|
SMTP_USER: z.string().optional(),
|
||||||
|
SMTP_SECURE: z.coerce.boolean().default(true),
|
||||||
|
|
||||||
AUTH_GOOGLE_ID: z.string().optional(),
|
AUTH_GOOGLE_ID: z.string().optional(),
|
||||||
AUTH_GOOGLE_SECRET: z.string().optional(),
|
AUTH_GOOGLE_SECRET: z.string().optional(),
|
||||||
@@ -60,6 +61,7 @@ export const env = createEnv({
|
|||||||
SMTP_HOST: process.env.SMTP_HOST,
|
SMTP_HOST: process.env.SMTP_HOST,
|
||||||
SMTP_PORT: process.env.SMTP_PORT,
|
SMTP_PORT: process.env.SMTP_PORT,
|
||||||
SMTP_USER: process.env.SMTP_USER,
|
SMTP_USER: process.env.SMTP_USER,
|
||||||
|
SMTP_SECURE: process.env.SMTP_SECURE,
|
||||||
|
|
||||||
AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID,
|
AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID,
|
||||||
AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET,
|
AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {ac, admin, orgAdmin, orgMember, orgOwner, pending, superadmin, user} fro
|
|||||||
import {headers} from "next/headers";
|
import {headers} from "next/headers";
|
||||||
import {count, eq} from "drizzle-orm";
|
import {count, eq} from "drizzle-orm";
|
||||||
import {MemberWithUser, OrganizationWithMembersAndUsers} from "@/db/schema/03_organization";
|
import {MemberWithUser, OrganizationWithMembersAndUsers} from "@/db/schema/03_organization";
|
||||||
import {sendEmail} from "@/lib/email/email-helper";
|
import {sendEmail} from "@/lib/email";
|
||||||
import {render} from "@react-email/render";
|
import {render} from "@react-email/render";
|
||||||
import {AuthProviderConfig, SUPPORTED_PROVIDERS} from "../../../portabase.config";
|
import {AuthProviderConfig, SUPPORTED_PROVIDERS} from "../../../portabase.config";
|
||||||
import {withUpdatedAt} from "@/db/utils";
|
import {withUpdatedAt} from "@/db/utils";
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
"use server";
|
|
||||||
|
|
||||||
import {db} from "@/db";
|
|
||||||
import {eq} from "drizzle-orm";
|
|
||||||
import nodemailer from "nodemailer";
|
|
||||||
import * as drizzleDb from "@/db";
|
|
||||||
|
|
||||||
type Payload = {
|
|
||||||
to: string;
|
|
||||||
from?: string;
|
|
||||||
subject: string;
|
|
||||||
html: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Server = {
|
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
user: string;
|
|
||||||
pass: string;
|
|
||||||
from: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type EmailCustomProps = {
|
|
||||||
data: Payload;
|
|
||||||
server: Server;
|
|
||||||
};
|
|
||||||
|
|
||||||
type EmailMassProps = {
|
|
||||||
data: Payload;
|
|
||||||
servers: Server[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendEmail = async (data: Payload) => {
|
|
||||||
const settings = await db
|
|
||||||
.select()
|
|
||||||
.from(drizzleDb.schemas.setting)
|
|
||||||
.where(eq(drizzleDb.schemas.setting.name, "system"))
|
|
||||||
.then((res) => res[0]);
|
|
||||||
|
|
||||||
if (!settings) {
|
|
||||||
throw new Error("SMTP system settings not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!settings.smtpHost || !settings.smtpPort || !settings.smtpUser || !settings.smtpPassword || !settings.smtpFrom) {
|
|
||||||
console.warn("Incomplete SMTP settings. Email not sent.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emailsArray = data.to.split(",")
|
|
||||||
.map(email => email.trim());
|
|
||||||
|
|
||||||
|
|
||||||
const transporter = nodemailer.createTransport({
|
|
||||||
pool: true,
|
|
||||||
host: settings.smtpHost ?? "",
|
|
||||||
port: parseInt(settings.smtpPort ?? "587"),
|
|
||||||
secure: true,
|
|
||||||
auth: {
|
|
||||||
user: settings.smtpUser ?? "",
|
|
||||||
pass: settings.smtpPassword ?? "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return await transporter.sendMail({
|
|
||||||
...data,
|
|
||||||
to: emailsArray,
|
|
||||||
from: settings.smtpFrom ?? undefined,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendCustomEmail = async (data: EmailCustomProps) => {
|
|
||||||
const transporter = nodemailer.createTransport({
|
|
||||||
secure: true,
|
|
||||||
replyTo: data.server.user,
|
|
||||||
host: data.server.host,
|
|
||||||
port: data.server.port,
|
|
||||||
auth: {
|
|
||||||
user: data.server.user,
|
|
||||||
pass: data.server.pass,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return await transporter.sendMail({
|
|
||||||
...data.data,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sendMassEmail = async (data: EmailMassProps) => {
|
|
||||||
for (const server of data.servers) {
|
|
||||||
const transporter = nodemailer.createTransport({
|
|
||||||
secure: true,
|
|
||||||
host: server.host,
|
|
||||||
port: server.port,
|
|
||||||
auth: {
|
|
||||||
user: server.user,
|
|
||||||
pass: server.pass,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return await transporter.sendMail({
|
|
||||||
...data.data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import nodemailer from "nodemailer";
|
||||||
|
|
||||||
|
export const createTransporter = (server: Server) => {
|
||||||
|
const portNumber = Number(server.port);
|
||||||
|
return nodemailer.createTransport({
|
||||||
|
pool: true,
|
||||||
|
host: server.host,
|
||||||
|
port: portNumber,
|
||||||
|
secure: server.secure ?? portNumber === 465,
|
||||||
|
auth: {
|
||||||
|
user: server.user,
|
||||||
|
pass: server.pass,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"use server"
|
||||||
|
import {db} from "@/db";
|
||||||
|
import {eq} from "drizzle-orm";
|
||||||
|
import * as drizzleDb from "@/db";
|
||||||
|
import {createTransporter} from "@/lib/email/helpers";
|
||||||
|
|
||||||
|
export const sendEmail = async (data: Payload) => {
|
||||||
|
const settings = await db
|
||||||
|
.select()
|
||||||
|
.from(drizzleDb.schemas.setting)
|
||||||
|
.where(eq(drizzleDb.schemas.setting.name, "system"))
|
||||||
|
.then((res) => res[0]);
|
||||||
|
|
||||||
|
if (!settings) {
|
||||||
|
throw new Error("SMTP system settings not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!settings.smtpHost || !settings.smtpPort || !settings.smtpUser || !settings.smtpPassword || !settings.smtpFrom) {
|
||||||
|
console.warn("Incomplete SMTP settings. Email not sent.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailsArray = data.to.split(",")
|
||||||
|
.map(email => email.trim());
|
||||||
|
|
||||||
|
const transporter = createTransporter({
|
||||||
|
host: settings.smtpHost,
|
||||||
|
port: Number(settings.smtpPort),
|
||||||
|
user: settings.smtpUser,
|
||||||
|
pass: settings.smtpPassword,
|
||||||
|
from: settings.smtpFrom,
|
||||||
|
secure: settings.smtpSecure ?? false,
|
||||||
|
});
|
||||||
|
await transporter.verify();
|
||||||
|
|
||||||
|
return await transporter.sendMail({
|
||||||
|
...data,
|
||||||
|
to: emailsArray,
|
||||||
|
from: settings.smtpFrom ?? undefined,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
|
||||||
|
type Payload = {
|
||||||
|
to: string;
|
||||||
|
from?: string;
|
||||||
|
subject: string;
|
||||||
|
html: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Server = {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
user: string;
|
||||||
|
pass: string;
|
||||||
|
from: string;
|
||||||
|
secure: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
@@ -18,9 +18,9 @@ export const retentionCleanTask = async () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
console.log(`Retention databases number: ${databases.length}`);
|
||||||
for (const db of databases) {
|
for (const db of databases) {
|
||||||
if (!db.retentionPolicy) continue; // no policy = skip
|
if (!db.retentionPolicy) continue;
|
||||||
await enforceRetention(db.id, db.retentionPolicy);
|
await enforceRetention(db.id, db.retentionPolicy);
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -33,7 +33,7 @@ export async function enforceRetention(
|
|||||||
databaseId: string,
|
databaseId: string,
|
||||||
policy: typeof retentionPolicy.$inferSelect
|
policy: typeof retentionPolicy.$inferSelect
|
||||||
) {
|
) {
|
||||||
|
console.log(`Retention started for ${databaseId}`);
|
||||||
switch (policy.type) {
|
switch (policy.type) {
|
||||||
case "count":
|
case "count":
|
||||||
await enforceRetentionCount(databaseId, policy.count ?? 7);
|
await enforceRetentionCount(databaseId, policy.count ?? 7);
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import * as drizzleDb from "@/db";
|
|||||||
import {db} from "@/db";
|
import {db} from "@/db";
|
||||||
import {and, desc, eq, isNull} from "drizzle-orm";
|
import {and, desc, eq, isNull} from "drizzle-orm";
|
||||||
import {deleteBackupCronAction} from "@/lib/tasks/database/utils/delete";
|
import {deleteBackupCronAction} from "@/lib/tasks/database/utils/delete";
|
||||||
|
import {toast} from "sonner";
|
||||||
|
|
||||||
export async function enforceRetentionCount(databaseId: string, count: number) {
|
export async function enforceRetentionCount(databaseId: string, count: number) {
|
||||||
|
console.log(`[Retention Count] - ${databaseId} : started`);
|
||||||
const backups = await db.query.backup.findMany({
|
const backups = await db.query.backup.findMany({
|
||||||
where: and(eq(drizzleDb.schemas.backup.databaseId, databaseId), isNull(drizzleDb.schemas.backup.deletedAt)),
|
where: and(eq(drizzleDb.schemas.backup.databaseId, databaseId), isNull(drizzleDb.schemas.backup.deletedAt)),
|
||||||
orderBy: desc(drizzleDb.schemas.backup.createdAt),
|
orderBy: desc(drizzleDb.schemas.backup.createdAt),
|
||||||
@@ -16,12 +18,20 @@ export async function enforceRetentionCount(databaseId: string, count: number) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const toDelete = backups.slice(count); // keep first `count`, delete rest
|
const toDelete = backups.slice(count);
|
||||||
|
console.log(`[Retention Count] - ${databaseId} : ${toDelete.length} backups to delete`);
|
||||||
|
|
||||||
for (const b of toDelete) {
|
for (const b of toDelete) {
|
||||||
await deleteBackupCronAction({
|
const result = await deleteBackupCronAction({
|
||||||
backupId: b.id,
|
backupId: b.id,
|
||||||
databaseId: b.databaseId,
|
databaseId: b.databaseId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const inner = result?.data;
|
||||||
|
if (inner?.success) {
|
||||||
|
console.log(`[Retention Count] - (databaseId:${b.databaseId}) - (backupId: ${b.id}) : successfully deleted`);
|
||||||
|
} else {
|
||||||
|
console.log(`[Retention Count] - (databaseId:${b.databaseId}) - (backupId: ${b.id}) : an error occurred - ${inner?.actionError?.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import * as drizzleDb from "@/db";
|
|||||||
import {deleteBackupCronAction} from "@/lib/tasks/database/utils/delete";
|
import {deleteBackupCronAction} from "@/lib/tasks/database/utils/delete";
|
||||||
|
|
||||||
export async function enforceRetentionDays(databaseId: string, days: number) {
|
export async function enforceRetentionDays(databaseId: string, days: number) {
|
||||||
|
console.log(`Enforce Retention Days starting for ${databaseId}`);
|
||||||
const cutoff = new Date(Date.now() - days * 86400000);
|
const cutoff = new Date(Date.now() - days * 86400000);
|
||||||
|
|
||||||
const expiredBackups = await db.query.backup.findMany({
|
const expiredBackups = await db.query.backup.findMany({
|
||||||
@@ -22,11 +23,18 @@ export async function enforceRetentionDays(databaseId: string, days: number) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const backup of expiredBackups) {
|
for (const backup of expiredBackups) {
|
||||||
await deleteBackupCronAction({
|
|
||||||
|
const result = await deleteBackupCronAction({
|
||||||
backupId: backup.id,
|
backupId: backup.id,
|
||||||
databaseId: backup.databaseId,
|
databaseId: backup.databaseId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const inner = result?.data;
|
||||||
|
if (inner?.success) {
|
||||||
|
console.log(`[Retention Days] - (databaseId:${backup.databaseId}) - (backupId: ${backup.id}) : successfully deleted`);
|
||||||
|
} else {
|
||||||
|
console.log(`[Retention Days] - (databaseId:${backup.databaseId}) - (backupId: ${backup.id}) : an error occurred - ${inner?.actionError?.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ export async function enforceRetentionGFS(databaseId: string, gfsSettings: {
|
|||||||
monthly: number;
|
monthly: number;
|
||||||
yearly: number;
|
yearly: number;
|
||||||
}) {
|
}) {
|
||||||
|
console.log(`Enforce Retention GFS starting for ${databaseId}`);
|
||||||
|
|
||||||
const backups = await db.query.backup.findMany({
|
const backups = await db.query.backup.findMany({
|
||||||
where: and(eq(drizzleDb.schemas.backup.databaseId, databaseId), isNull(drizzleDb.schemas.backup.deletedAt)),
|
where: and(eq(drizzleDb.schemas.backup.databaseId, databaseId), isNull(drizzleDb.schemas.backup.deletedAt)),
|
||||||
orderBy: desc(drizzleDb.schemas.backup.createdAt),
|
orderBy: desc(drizzleDb.schemas.backup.createdAt),
|
||||||
@@ -60,10 +62,17 @@ export async function enforceRetentionGFS(databaseId: string, gfsSettings: {
|
|||||||
for (const b of backups) {
|
for (const b of backups) {
|
||||||
if (!toKeep.has(b.id)) {
|
if (!toKeep.has(b.id)) {
|
||||||
|
|
||||||
await deleteBackupCronAction({
|
const result = await deleteBackupCronAction({
|
||||||
backupId: b.id,
|
backupId: b.id,
|
||||||
databaseId: b.databaseId,
|
databaseId: b.databaseId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const inner = result?.data;
|
||||||
|
if (inner?.success) {
|
||||||
|
console.log(`[Retention GFS] - (databaseId:${b.databaseId}) - (backupId: ${b.id}) : successfully deleted`);
|
||||||
|
} else {
|
||||||
|
console.log(`[Retention GFS] - (databaseId:${b.databaseId}) - (backupId: ${b.id}) : an error occurred - ${inner?.actionError?.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ export const deleteBackupCronAction = action
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
const backup = await db.query.backup.findFirst({
|
const backup = await db.query.backup.findFirst({
|
||||||
where: and(eq(drizzleDb.schemas.backup.id, parsedInput.backupId), eq(drizzleDb.schemas.backup.databaseId, parsedInput.backupId))
|
where: and(eq(drizzleDb.schemas.backup.id, parsedInput.backupId), eq(drizzleDb.schemas.backup.databaseId, parsedInput.databaseId))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (!backup) {
|
if (!backup) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -42,17 +43,6 @@ export const deleteBackupCronAction = action
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (!backupStorages) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
actionError: {
|
|
||||||
message: "Backup storage not found.",
|
|
||||||
status: 404,
|
|
||||||
messageParams: {backupId: parsedInput.backupId},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const backupStorage of backupStorages) {
|
for (const backupStorage of backupStorages) {
|
||||||
|
|
||||||
await db
|
await db
|
||||||
@@ -81,7 +71,7 @@ export const deleteBackupCronAction = action
|
|||||||
.set(withUpdatedAt({
|
.set(withUpdatedAt({
|
||||||
deletedAt: new Date(),
|
deletedAt: new Date(),
|
||||||
}))
|
}))
|
||||||
.where(and(eq(drizzleDb.schemas.backup.id, parsedInput.backupId), eq(drizzleDb.schemas.backup.databaseId, parsedInput.backupId)))
|
.where(and(eq(drizzleDb.schemas.backup.id, parsedInput.backupId), eq(drizzleDb.schemas.backup.databaseId, parsedInput.databaseId)))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
+1
-2
@@ -4,8 +4,6 @@ import {eq} from "drizzle-orm";
|
|||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
import {retentionJob} from "@/lib/tasks";
|
import {retentionJob} from "@/lib/tasks";
|
||||||
import {generateRSAKeys} from "@/utils/rsa-keys";
|
import {generateRSAKeys} from "@/utils/rsa-keys";
|
||||||
import {Provider} from "react";
|
|
||||||
import type {ProviderKind} from "@/features/notifications/types";
|
|
||||||
import {StorageProviderKind} from "@/features/storages/types";
|
import {StorageProviderKind} from "@/features/storages/types";
|
||||||
|
|
||||||
|
|
||||||
@@ -34,6 +32,7 @@ async function createSettingsIfNotExist() {
|
|||||||
smtpHost: env.SMTP_HOST ?? null,
|
smtpHost: env.SMTP_HOST ?? null,
|
||||||
smtpPort: env.SMTP_PORT ?? null,
|
smtpPort: env.SMTP_PORT ?? null,
|
||||||
smtpUser: env.SMTP_USER ?? null,
|
smtpUser: env.SMTP_USER ?? null,
|
||||||
|
smtpSecure: env.SMTP_SECURE,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [existing] = await db.select().from(drizzleDb.schemas.setting).where(eq(drizzleDb.schemas.setting.name, "system")).limit(1);
|
const [existing] = await db.select().from(drizzleDb.schemas.setting).where(eq(drizzleDb.schemas.setting.name, "system")).limit(1);
|
||||||
|
|||||||
Reference in New Issue
Block a user