mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8edf0ca742 | ||
|
|
568d9612c5 |
+1
-1
@@ -33,5 +33,5 @@ keywords:
|
|||||||
- web-ui
|
- web-ui
|
||||||
- agent
|
- agent
|
||||||
license: Apache-2.0
|
license: Apache-2.0
|
||||||
version: 1.21.0
|
version: 1.21.1
|
||||||
date-released: '2026-03-02'
|
date-released: '2026-03-02'
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {Agent} from "@/db/schema/08_agent";
|
|||||||
import {DatabaseWith} from "@/db/schema/07_database";
|
import {DatabaseWith} from "@/db/schema/07_database";
|
||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
import {db, db as dbClient} from "@/db";
|
import {db, db as dbClient} from "@/db";
|
||||||
import {and, eq, inArray} from "drizzle-orm";
|
import {and, eq, inArray, desc, sql} from "drizzle-orm";
|
||||||
import {dbmsEnumSchema, EDbmsSchema} from "@/db/schema/types";
|
import {dbmsEnumSchema, EDbmsSchema} from "@/db/schema/types";
|
||||||
import {withUpdatedAt} from "@/db/utils";
|
import {withUpdatedAt} from "@/db/utils";
|
||||||
import {Setting} from "@/db/schema/01_setting";
|
import {Setting} from "@/db/schema/01_setting";
|
||||||
@@ -123,7 +123,11 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat
|
|||||||
where: and(
|
where: and(
|
||||||
eq(drizzleDb.schemas.backup.databaseId, databaseUpdated.id),
|
eq(drizzleDb.schemas.backup.databaseId, databaseUpdated.id),
|
||||||
inArray(drizzleDb.schemas.backup.status, ["waiting", "ongoing"])
|
inArray(drizzleDb.schemas.backup.status, ["waiting", "ongoing"])
|
||||||
)
|
),
|
||||||
|
orderBy: [
|
||||||
|
sql`case when ${drizzleDb.schemas.backup.status} = 'waiting' then 0 else 1 end`,
|
||||||
|
desc(drizzleDb.schemas.backup.createdAt)
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const restoration = await dbClient.query.restoration.findFirst({
|
const restoration = await dbClient.query.restoration.findFirst({
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "portabase",
|
"name": "portabase",
|
||||||
"version": "1.21.0",
|
"version": "1.21.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack -p 8887",
|
"dev": "next dev --turbopack -p 8887",
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export const env = createEnv({
|
|||||||
process.env.NODE_ENV === "production" ? "0 * * * *" : "* * * * *",
|
process.env.NODE_ENV === "production" ? "0 * * * *" : "* * * * *",
|
||||||
),
|
),
|
||||||
|
|
||||||
|
STALE_BACKUP_THRESHOLD_HOURS: z.coerce.number().default(6),
|
||||||
|
|
||||||
AUTH_OIDC_ID: z.string().optional().default("oidc"),
|
AUTH_OIDC_ID: z.string().optional().default("oidc"),
|
||||||
AUTH_OIDC_TITLE: z.string().optional(),
|
AUTH_OIDC_TITLE: z.string().optional(),
|
||||||
@@ -147,6 +148,7 @@ export const env = createEnv({
|
|||||||
|
|
||||||
RETENTION_CRON: process.env.RETENTION_CRON,
|
RETENTION_CRON: process.env.RETENTION_CRON,
|
||||||
CLEANING_HEALTHCHECK_LOGS_CRON: process.env.CLEANING_HEALTHCHECK_LOGS_CRON,
|
CLEANING_HEALTHCHECK_LOGS_CRON: process.env.CLEANING_HEALTHCHECK_LOGS_CRON,
|
||||||
|
STALE_BACKUP_THRESHOLD_HOURS: process.env.STALE_BACKUP_THRESHOLD_HOURS,
|
||||||
|
|
||||||
AUTH_OIDC_ID: process.env.AUTH_OIDC_ID,
|
AUTH_OIDC_ID: process.env.AUTH_OIDC_ID,
|
||||||
AUTH_OIDC_TITLE: process.env.AUTH_OIDC_TITLE,
|
AUTH_OIDC_TITLE: process.env.AUTH_OIDC_TITLE,
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import {db} from "@/db";
|
import {db} from "@/db";
|
||||||
import {and, eq, isNotNull, isNull} from "drizzle-orm";
|
import {and, eq, isNotNull, isNull, lt} from "drizzle-orm";
|
||||||
import * as drizzleDb from "@/db";
|
import * as drizzleDb from "@/db";
|
||||||
import {withUpdatedAt} from "@/db/utils";
|
import {withUpdatedAt} from "@/db/utils";
|
||||||
import {logger} from "@/lib/logger";
|
import {logger} from "@/lib/logger";
|
||||||
|
import {env} from "@/env.mjs";
|
||||||
|
import {sendNotificationsBackupRestore} from "@/features/notifications/utils/notifications.helpers";
|
||||||
|
|
||||||
const log = logger.child({module: "tasks/cleaning"});
|
const log = logger.child({module: "tasks/cleaning"});
|
||||||
|
|
||||||
@@ -23,6 +25,36 @@ export const backupCleanTask = async () => {
|
|||||||
.where(eq(drizzleDb.schemas.backup.id, backup.id));
|
.where(eq(drizzleDb.schemas.backup.id, backup.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const staleCutoff = new Date(Date.now() - env.STALE_BACKUP_THRESHOLD_HOURS * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
const staleOngoingBackups = await db.query.backup.findMany({
|
||||||
|
where: and(
|
||||||
|
isNull(drizzleDb.schemas.backup.deletedAt),
|
||||||
|
eq(drizzleDb.schemas.backup.status, "ongoing"),
|
||||||
|
lt(drizzleDb.schemas.backup.createdAt, staleCutoff)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
log.debug(`Stale ongoing backups to fail: ${staleOngoingBackups.length}`);
|
||||||
|
|
||||||
|
for (const backup of staleOngoingBackups) {
|
||||||
|
await db.update(drizzleDb.schemas.backup).set(withUpdatedAt({
|
||||||
|
status: "failed",
|
||||||
|
}))
|
||||||
|
.where(eq(drizzleDb.schemas.backup.id, backup.id));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const database = await db.query.database.findFirst({
|
||||||
|
where: eq(drizzleDb.schemas.database.id, backup.databaseId),
|
||||||
|
with: {alertPolicies: true},
|
||||||
|
});
|
||||||
|
if (database) {
|
||||||
|
await sendNotificationsBackupRestore(database, "error_backup");
|
||||||
|
}
|
||||||
|
} catch (notifyError) {
|
||||||
|
log.error({name: "backupCleanTask", error: notifyError}, "Stale backup notification failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const failedBackups = await db.query.backup.findMany({
|
const failedBackups = await db.query.backup.findMany({
|
||||||
where: and(
|
where: and(
|
||||||
isNull(drizzleDb.schemas.backup.deletedAt),
|
isNull(drizzleDb.schemas.backup.deletedAt),
|
||||||
|
|||||||
Reference in New Issue
Block a user