diff --git a/app/(customer)/dashboard/(organization)/projects/[projectId]/edit/page.tsx b/app/(customer)/dashboard/(organization)/projects/[projectId]/edit/page.tsx index 2fae522d..0808e643 100644 --- a/app/(customer)/dashboard/(organization)/projects/[projectId]/edit/page.tsx +++ b/app/(customer)/dashboard/(organization)/projects/[projectId]/edit/page.tsx @@ -6,7 +6,7 @@ import * as drizzleDb from "@/db"; import {db} from "@/db"; import {eq} from "drizzle-orm"; -import {DatabaseWith} from "@/db/schema/06_database"; +import {DatabaseWith} from "@/db/schema/07_database"; export default async function RoutePage(props: PageParams<{ projectId: string }>) { const {projectId} = await props.params; diff --git a/app/(customer)/dashboard/(organization)/projects/new/page.tsx b/app/(customer)/dashboard/(organization)/projects/new/page.tsx index f83afdcc..ea69db09 100644 --- a/app/(customer)/dashboard/(organization)/projects/new/page.tsx +++ b/app/(customer)/dashboard/(organization)/projects/new/page.tsx @@ -6,7 +6,7 @@ import {db} from "@/db"; import {eq} from "drizzle-orm"; import {getOrganization} from "@/lib/auth/auth"; import * as drizzleDb from "@/db"; -import {DatabaseWith} from "@/db/schema/06_database"; +import {DatabaseWith} from "@/db/schema/07_database"; export default async function RoutePage(props: PageParams<{ }>) { diff --git a/app/api/agent/[agentId]/backup/route.ts b/app/api/agent/[agentId]/backup/route.ts index 90ca43c2..0c2ad351 100644 --- a/app/api/agent/[agentId]/backup/route.ts +++ b/app/api/agent/[agentId]/backup/route.ts @@ -4,10 +4,11 @@ import {uploadLocalPrivate, uploadS3Private} from "@/features/upload/private/upl import {v4 as uuidv4} from "uuid"; import {eventEmitter} from "../../../events/route"; import {db} from "@/db"; -import {Backup} from "@/db/schema/06_database"; +import {Backup} from "@/db/schema/07_database"; import {and, eq} from "drizzle-orm"; import * as drizzleDb from "@/db"; import {env} from "@/env.mjs"; +import {withUpdatedAt} from "@/db/utils"; export async function POST( request: Request, @@ -135,10 +136,10 @@ export async function POST( await db .update(drizzleDb.schemas.backup) - .set({ + .set(withUpdatedAt({ file: fileName, status: 'success', - }) + })) .where(eq(drizzleDb.schemas.backup.id, backup.id)); eventEmitter.emit('modification', {update: true}); @@ -153,7 +154,7 @@ export async function POST( await db .update(drizzleDb.schemas.backup) - .set({status: 'failed'}) + .set(withUpdatedAt({status: 'failed'})) .where(eq(drizzleDb.schemas.backup.id, backup.id)); eventEmitter.emit('modification', {update: true}); diff --git a/app/api/agent/[agentId]/status/helpers.ts b/app/api/agent/[agentId]/status/helpers.ts index 6fefbf38..f40476b7 100644 --- a/app/api/agent/[agentId]/status/helpers.ts +++ b/app/api/agent/[agentId]/status/helpers.ts @@ -2,8 +2,8 @@ import {NextResponse} from "next/server"; import {Body} from "./route"; import {isUuidv4} from "@/utils/verify-uuid"; import {getFileUrlPresignedLocal, getFileUrlPreSignedS3Action} from "@/features/upload/private/upload.action"; -import {Agent} from "@/db/schema/07_agent"; -import {Database} from "@/db/schema/06_database"; +import {Agent} from "@/db/schema/08_agent"; +import {Database} from "@/db/schema/07_database"; import * as drizzleDb from "@/db"; import {db as dbClient} from "@/db"; import {and, eq} from "drizzle-orm"; @@ -11,6 +11,7 @@ import {dbmsEnumSchema, EDbmsSchema} from "@/db/schema/types"; import {ServerActionResult} from "@/types/action-type"; import {SafeActionResult} from "next-safe-action"; import {ZodString} from "zod"; +import {withUpdatedAt} from "@/db/utils"; export async function handleDatabases(body: Body, agent: Agent, lastContact: Date) { const databasesResponse = []; @@ -88,7 +89,7 @@ export async function handleDatabases(body: Body, agent: Agent, lastContact: Dat await dbClient .update(drizzleDb.schemas.backup) - .set({status: "ongoing"}) + .set(withUpdatedAt({status: "ongoing"})) .where(eq(drizzleDb.schemas.backup.id, backup.id)); } diff --git a/src/components/wrappers/dashboard/admin/admin-email-tab/settings-email-tab.tsx b/src/components/wrappers/dashboard/admin/admin-email-tab/settings-email-tab.tsx index 420d9872..4a56bc24 100644 --- a/src/components/wrappers/dashboard/admin/admin-email-tab/settings-email-tab.tsx +++ b/src/components/wrappers/dashboard/admin/admin-email-tab/settings-email-tab.tsx @@ -6,7 +6,7 @@ import { sendEmail } from "@/utils/email-helper"; import TestEmailSettings from "../../../../../../emails/TestEmailSettings"; import { render } from "@react-email/render"; import { toast } from "sonner"; -import {Setting} from "@/db/schema/00_setting"; +import {Setting} from "@/db/schema/01_setting"; import {EmailFormType} from "@/components/wrappers/dashboard/admin/admin-email-tab/email-form/email-form.schema"; export type SettingsEmailTabProps = { diff --git a/src/components/wrappers/dashboard/admin/admin-storage-tab/settings-storage-tab.tsx b/src/components/wrappers/dashboard/admin/admin-storage-tab/settings-storage-tab.tsx index 172bee99..40788a23 100644 --- a/src/components/wrappers/dashboard/admin/admin-storage-tab/settings-storage-tab.tsx +++ b/src/components/wrappers/dashboard/admin/admin-storage-tab/settings-storage-tab.tsx @@ -12,7 +12,7 @@ import {useRouter} from "next/navigation"; import { updateStorageSettingsAction } from "@/components/wrappers/dashboard/admin/admin-storage-tab/storage-s3/s3-form.action"; -import {Setting} from "@/db/schema/00_setting"; +import {Setting} from "@/db/schema/01_setting"; import {S3FormType} from "@/components/wrappers/dashboard/admin/admin-storage-tab/storage-s3/s3-form.schema"; export type SettingsStorageTabProps = { diff --git a/src/components/wrappers/dashboard/admin/admin-tabs.tsx b/src/components/wrappers/dashboard/admin/admin-tabs.tsx index 9a9de4ef..b64bb14c 100644 --- a/src/components/wrappers/dashboard/admin/admin-tabs.tsx +++ b/src/components/wrappers/dashboard/admin/admin-tabs.tsx @@ -3,8 +3,8 @@ import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs"; import {SettingsEmailTab} from "@/components/wrappers/dashboard/admin/admin-email-tab/settings-email-tab"; import {SettingsStorageTab} from "@/components/wrappers/dashboard/admin/admin-storage-tab/settings-storage-tab"; -import {User, UserWithAccounts} from "@/db/schema/01_user"; -import {Setting} from "@/db/schema/00_setting"; +import {User, UserWithAccounts} from "@/db/schema/02_user"; +import {Setting} from "@/db/schema/01_setting"; import {useEffect, useState} from "react"; import {useRouter, useSearchParams} from "next/navigation"; import {AdminUsersTable} from "@/components/wrappers/dashboard/admin/admin-user-tab/admin-user-table"; diff --git a/src/components/wrappers/dashboard/admin/admin-user-tab/admin-user-table.tsx b/src/components/wrappers/dashboard/admin/admin-user-tab/admin-user-table.tsx index 5045ffc4..02aa4bec 100644 --- a/src/components/wrappers/dashboard/admin/admin-user-tab/admin-user-table.tsx +++ b/src/components/wrappers/dashboard/admin/admin-user-tab/admin-user-table.tsx @@ -1,4 +1,4 @@ -import {User, UserWithAccounts} from "@/db/schema/01_user"; +import {User, UserWithAccounts} from "@/db/schema/02_user"; import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card"; import {DataTable} from "@/components/wrappers/common/table/data-table"; import {usersColumnsAdmin} from "@/components/wrappers/dashboard/admin/admin-user-tab/columns-users"; diff --git a/src/components/wrappers/dashboard/admin/admin-user-tab/columns-users.tsx b/src/components/wrappers/dashboard/admin/admin-user-tab/columns-users.tsx index c9d85bb3..4661a624 100644 --- a/src/components/wrappers/dashboard/admin/admin-user-tab/columns-users.tsx +++ b/src/components/wrappers/dashboard/admin/admin-user-tab/columns-users.tsx @@ -9,7 +9,7 @@ import {useState} from "react"; import {Trash2} from "lucide-react"; import {deleteUserAction} from "@/components/wrappers/dashboard/profile/button-delete-account/delete-account.action"; import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading"; -import {UserWithAccounts} from "@/db/schema/01_user"; +import {UserWithAccounts} from "@/db/schema/02_user"; import {authClient, useSession} from "@/lib/auth/auth-client"; import {formatFrenchDate} from "@/utils/date-formatting"; import {providerSwitch} from "@/components/wrappers/common/provider-switch"; diff --git a/src/components/wrappers/dashboard/agent/agent-card-key/agent-card-key.tsx b/src/components/wrappers/dashboard/agent/agent-card-key/agent-card-key.tsx index ff84259d..fbcfc11c 100644 --- a/src/components/wrappers/dashboard/agent/agent-card-key/agent-card-key.tsx +++ b/src/components/wrappers/dashboard/agent/agent-card-key/agent-card-key.tsx @@ -4,7 +4,7 @@ import {getServerUrl} from "@/utils/get-server-url"; import {PasswordInput} from "@/components/wrappers/auth/password-input/password-input"; import {useState} from "react"; import {CopyButton} from "@/components/wrappers/common/button/copy-button"; -import {Agent} from "@/db/schema/07_agent"; +import {Agent} from "@/db/schema/08_agent"; export type AgentCardKeyProps = { agent: Agent; diff --git a/src/components/wrappers/dashboard/agent/agent-card/agent-card.tsx b/src/components/wrappers/dashboard/agent/agent-card/agent-card.tsx index 142072d2..841084b1 100644 --- a/src/components/wrappers/dashboard/agent/agent-card/agent-card.tsx +++ b/src/components/wrappers/dashboard/agent/agent-card/agent-card.tsx @@ -4,7 +4,7 @@ import { Card, CardContent, CardHeader } from "@/components/ui/card"; import Link from "next/link"; import { formatDateLastContact } from "@/utils/date-formatting"; import { ConnectionCircle } from "@/components/wrappers/common/connection-circle"; -import {Agent} from "@/db/schema/07_agent"; +import {Agent} from "@/db/schema/08_agent"; export type agentCardProps = { data: Agent; diff --git a/src/components/wrappers/dashboard/agent/agent-modal-key/agent-modal-key.tsx b/src/components/wrappers/dashboard/agent/agent-modal-key/agent-modal-key.tsx index 58c80dd3..5ccbef18 100644 --- a/src/components/wrappers/dashboard/agent/agent-modal-key/agent-modal-key.tsx +++ b/src/components/wrappers/dashboard/agent/agent-modal-key/agent-modal-key.tsx @@ -8,7 +8,7 @@ import { PropsWithChildren } from "react"; import { CopyButton } from "@/components/wrappers/common/button/copy-button"; import { getServerUrl } from "@/utils/get-server-url"; import { CodeSnippet } from "@/components/wrappers/code-snippet/code-snippet"; -import {Agent} from "@/db/schema/07_agent"; +import {Agent} from "@/db/schema/08_agent"; export type agentRegistrationDialogProps = PropsWithChildren<{ agent: Agent; diff --git a/src/components/wrappers/dashboard/agent/button-delete-agent/delete-agent.action.ts b/src/components/wrappers/dashboard/agent/button-delete-agent/delete-agent.action.ts index 771b4d0a..da5f1eb8 100644 --- a/src/components/wrappers/dashboard/agent/button-delete-agent/delete-agent.action.ts +++ b/src/components/wrappers/dashboard/agent/button-delete-agent/delete-agent.action.ts @@ -7,7 +7,7 @@ import {ServerActionResult} from "@/types/action-type"; import {eq} from "drizzle-orm"; import {db} from "@/db"; import * as drizzleDb from "@/db"; -import {Agent} from "@/db/schema/07_agent"; +import {Agent} from "@/db/schema/08_agent"; export const deleteAgentAction = userAction.schema(z.string()).action(async ({parsedInput}): Promise> => { try { diff --git a/src/components/wrappers/dashboard/backup/backup-button/backup-button.action.ts b/src/components/wrappers/dashboard/backup/backup-button/backup-button.action.ts index 630a01ac..5d15013f 100644 --- a/src/components/wrappers/dashboard/backup/backup-button/backup-button.action.ts +++ b/src/components/wrappers/dashboard/backup/backup-button/backup-button.action.ts @@ -1,13 +1,14 @@ "use server"; -import { z } from "zod"; -import { userAction } from "@/safe-actions"; -import { db } from "@/db"; -import { ServerActionResult } from "@/types/action-type"; +import {z} from "zod"; +import {userAction} from "@/safe-actions"; +import {db} from "@/db"; +import {ServerActionResult} from "@/types/action-type"; import * as drizzleDb from "@/db"; -import {Backup} from "@/db/schema/06_database"; +import {Backup} from "@/db/schema/07_database"; +import {withUpdatedAt} from "@/db/utils"; -export const backupButtonAction = userAction.schema(z.string()).action(async ({ parsedInput }): Promise> => { +export const backupButtonAction = userAction.schema(z.string()).action(async ({parsedInput}): Promise> => { try { const [createdBackup] = await db .insert(drizzleDb.schemas.backup) @@ -22,7 +23,7 @@ export const backupButtonAction = userAction.schema(z.string()).action(async ({ value: createdBackup, actionSuccess: { message: "Backup has been successfully created.", - messageParams: { databaseId: parsedInput }, + messageParams: {databaseId: parsedInput}, }, }; } catch (error) { @@ -34,7 +35,7 @@ export const backupButtonAction = userAction.schema(z.string()).action(async ({ message: "Failed to create backup.", status: 500, cause: error instanceof Error ? error.message : "Unknown error", - messageParams: { databaseId: parsedInput }, + messageParams: {databaseId: parsedInput}, }, }; } diff --git a/src/components/wrappers/dashboard/common/logged-in/logged-in-dropdown.tsx b/src/components/wrappers/dashboard/common/logged-in/logged-in-dropdown.tsx index ff16ca29..9f050051 100644 --- a/src/components/wrappers/dashboard/common/logged-in/logged-in-dropdown.tsx +++ b/src/components/wrappers/dashboard/common/logged-in/logged-in-dropdown.tsx @@ -6,7 +6,7 @@ import { redirect } from "next/navigation"; import { CircleUser, LogOut, ShieldHalf } from "lucide-react"; import { signOut } from "@/lib/auth/auth-client"; import { useRouter } from "next/navigation"; -import { User } from "@/db/schema/01_user"; +import { User } from "@/db/schema/02_user"; export type LoggedInDropdownProps = PropsWithChildren<{ user: User; diff --git a/src/components/wrappers/dashboard/database/cron-button/cron-button.tsx b/src/components/wrappers/dashboard/database/cron-button/cron-button.tsx index 573eb708..31518cfe 100644 --- a/src/components/wrappers/dashboard/database/cron-button/cron-button.tsx +++ b/src/components/wrappers/dashboard/database/cron-button/cron-button.tsx @@ -11,7 +11,7 @@ import { useMutation } from "@tanstack/react-query"; import { toast } from "sonner"; import { useRouter } from "next/navigation"; import { updateDatabaseBackupPolicyAction } from "@/components/wrappers/dashboard/database/cron-button/cron.action"; -import {Database} from "@/db/schema/06_database"; +import {Database} from "@/db/schema/07_database"; export type CronButtonProps = { database: Database; diff --git a/src/components/wrappers/dashboard/database/cron-button/cron-input.tsx b/src/components/wrappers/dashboard/database/cron-button/cron-input.tsx index 7f199ef6..6b0008f8 100644 --- a/src/components/wrappers/dashboard/database/cron-button/cron-input.tsx +++ b/src/components/wrappers/dashboard/database/cron-button/cron-input.tsx @@ -6,7 +6,7 @@ import {useRouter} from "next/navigation"; import {toast} from "sonner"; import {Button} from "@/components/ui/button"; import {Separator} from "@/components/ui/separator"; -import {Database} from "@/db/schema/06_database"; +import {Database} from "@/db/schema/07_database"; export type CronInputProps = { database: Database; diff --git a/src/components/wrappers/dashboard/organization/organization-form/organization-form.tsx b/src/components/wrappers/dashboard/organization/organization-form/organization-form.tsx index 3ea7db23..b7e0011a 100644 --- a/src/components/wrappers/dashboard/organization/organization-form/organization-form.tsx +++ b/src/components/wrappers/dashboard/organization/organization-form/organization-form.tsx @@ -20,14 +20,14 @@ import { OrganizationFormSchema, OrganizationFormType } from "@/components/wrappers/dashboard/organization/organization-form/organization-form.schema"; -import {MemberWithUser, OrganizationWithMembers} from "@/db/schema/02_organization"; +import {MemberWithUser, OrganizationWithMembers} from "@/db/schema/03_organization"; import { deleteOrganizationAction, updateOrganizationAction } from "@/components/wrappers/dashboard/organization/organization.action"; import {toast} from "sonner"; import {User as BetterAuthUser} from "better-auth"; -import {User} from "@/db/schema/01_user"; +import {User} from "@/db/schema/02_user"; export type organizationFormProps = { defaultValues?: OrganizationWithMembers; diff --git a/src/components/wrappers/dashboard/organization/organization.action.ts b/src/components/wrappers/dashboard/organization/organization.action.ts index e5bb4e5e..04b2ebe3 100644 --- a/src/components/wrappers/dashboard/organization/organization.action.ts +++ b/src/components/wrappers/dashboard/organization/organization.action.ts @@ -11,7 +11,7 @@ import {db} from "@/db"; import {and, eq, inArray} from "drizzle-orm"; import {auth, checkSlugOrganization, createOrganization, deleteOrganization} from "@/lib/auth/auth"; import {slugify} from "@/utils/slugify"; -import {Organization} from "@/db/schema/02_organization"; +import {Organization} from "@/db/schema/03_organization"; import * as drizzleDb from "@/db"; import {headers} from "next/headers"; diff --git a/src/components/wrappers/dashboard/profile/avatar/avatar-with-upload.tsx b/src/components/wrappers/dashboard/profile/avatar/avatar-with-upload.tsx index 60f10264..6ed9b638 100644 --- a/src/components/wrappers/dashboard/profile/avatar/avatar-with-upload.tsx +++ b/src/components/wrappers/dashboard/profile/avatar/avatar-with-upload.tsx @@ -6,7 +6,7 @@ import { uploadImageAction } from "@/features/upload/public/upload.action"; import { useMutation } from "@tanstack/react-query"; import { updateImageUserAction } from "@/components/wrappers/dashboard/profile/avatar/avatar.action"; import { useRouter } from "next/navigation"; -import { User } from "@/db/schema/01_user"; +import { User } from "@/db/schema/02_user"; import {ChangeEvent} from "react"; export type AvatarWithUploadProps = { diff --git a/src/components/wrappers/dashboard/projects/database/database-kpi.tsx b/src/components/wrappers/dashboard/projects/database/database-kpi.tsx index ac6d9017..a0f0236f 100644 --- a/src/components/wrappers/dashboard/projects/database/database-kpi.tsx +++ b/src/components/wrappers/dashboard/projects/database/database-kpi.tsx @@ -2,7 +2,7 @@ import {Card, CardContent, CardHeader} from "@/components/ui/card"; import {formatDateLastContact} from "@/utils/date-formatting"; -import {Database} from "@/db/schema/06_database"; +import {Database} from "@/db/schema/07_database"; export type DatabaseKpiPro = { successRate: any; diff --git a/src/components/wrappers/dashboard/projects/database/database-tabs.tsx b/src/components/wrappers/dashboard/projects/database/database-tabs.tsx index f82e7fa0..42a7168f 100644 --- a/src/components/wrappers/dashboard/projects/database/database-tabs.tsx +++ b/src/components/wrappers/dashboard/projects/database/database-tabs.tsx @@ -7,8 +7,8 @@ import {eventUpdate} from "@/types/events"; import {backupColumns} from "@/features/dashboard/backup/columns"; import {restoreColumns} from "@/features/dashboard/restore/columns"; import {DataTable} from "@/components/wrappers/common/table/data-table"; -import {Backup, Database, DatabaseWith, Restoration} from "@/db/schema/06_database"; -import {Setting} from "@/db/schema/00_setting"; +import {Backup, Database, DatabaseWith, Restoration} from "@/db/schema/07_database"; +import {Setting} from "@/db/schema/01_setting"; import {DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger} from "@/components/ui/dropdown-menu"; import {MoreHorizontal, Trash2} from "lucide-react"; import {useMutation} from "@tanstack/react-query"; @@ -62,6 +62,8 @@ export const DatabaseTabs = (props: DatabaseTabsProps) => { const backupDeleted = await deleteBackupAction({ backupId: backup.id, databaseId: backup.databaseId, + file: backup.file!, + projectSlug: props.database?.project?.slug! }); return { success: backupDeleted?.data?.success, diff --git a/src/components/wrappers/dashboard/projects/project-card/project-card.tsx b/src/components/wrappers/dashboard/projects/project-card/project-card.tsx index 68ff9f8a..410e1716 100644 --- a/src/components/wrappers/dashboard/projects/project-card/project-card.tsx +++ b/src/components/wrappers/dashboard/projects/project-card/project-card.tsx @@ -2,7 +2,7 @@ import { Card, CardContent, CardHeader } from "@/components/ui/card"; import Link from "next/link"; -import {ProjectWith} from "@/db/schema/05_project"; +import {ProjectWith} from "@/db/schema/06_project"; export type projectCardProps = { data: ProjectWith; diff --git a/src/components/wrappers/dashboard/projects/project-card/project-database-card.tsx b/src/components/wrappers/dashboard/projects/project-card/project-database-card.tsx index 1bc26c33..00f244d4 100644 --- a/src/components/wrappers/dashboard/projects/project-card/project-database-card.tsx +++ b/src/components/wrappers/dashboard/projects/project-card/project-database-card.tsx @@ -5,7 +5,7 @@ import Image from "next/image"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { ConnectionCircle } from "@/components/wrappers/common/connection-circle"; import { formatDateLastContact } from "@/utils/date-formatting"; -import {Database} from "@/db/schema/06_database"; +import {Database} from "@/db/schema/07_database"; import {Avatar, AvatarFallback, AvatarImage} from "@/components/ui/avatar"; export type projectDatabaseCardProps = { diff --git a/src/components/wrappers/dashboard/projects/project-form/project-form.action.ts b/src/components/wrappers/dashboard/projects/project-form/project-form.action.ts index 4c3dfe7d..bd432eed 100644 --- a/src/components/wrappers/dashboard/projects/project-form/project-form.action.ts +++ b/src/components/wrappers/dashboard/projects/project-form/project-form.action.ts @@ -6,9 +6,9 @@ import { z } from "zod"; import { ServerActionResult } from "@/types/action-type"; import { db } from "@/db"; import { eq, inArray } from "drizzle-orm"; -import {Project} from "@/db/schema/05_project"; +import {Project} from "@/db/schema/06_project"; import * as drizzleDb from "@/db"; -import {Database} from "@/db/schema/06_database"; +import {Database} from "@/db/schema/07_database"; import {slugify} from "@/utils/slugify"; export const createProjectAction = userAction diff --git a/src/components/wrappers/dashboard/projects/project-form/project-form.tsx b/src/components/wrappers/dashboard/projects/project-form/project-form.tsx index 3f919751..96c283f8 100644 --- a/src/components/wrappers/dashboard/projects/project-form/project-form.tsx +++ b/src/components/wrappers/dashboard/projects/project-form/project-form.tsx @@ -11,8 +11,8 @@ import { createProjectAction, updateProjectAction } from "@/components/wrappers/ import { useRouter } from "next/navigation"; import { MultiSelect } from "@/components/wrappers/common/multiselect/multi-select"; import { toast } from "sonner"; -import {DatabaseWith} from "@/db/schema/06_database"; -import {Organization} from "@/db/schema/02_organization"; +import {DatabaseWith} from "@/db/schema/07_database"; +import {Organization} from "@/db/schema/03_organization"; export type projectFormProps = { defaultValues?: ProjectType; diff --git a/src/components/wrappers/dashboard/settings/columns-organization-members.tsx b/src/components/wrappers/dashboard/settings/columns-organization-members.tsx index 0af4dc07..9c014597 100644 --- a/src/components/wrappers/dashboard/settings/columns-organization-members.tsx +++ b/src/components/wrappers/dashboard/settings/columns-organization-members.tsx @@ -1,7 +1,7 @@ "use client"; import {ColumnDef} from "@tanstack/react-table"; -import {MemberWithUser} from "@/db/schema/02_organization"; +import {MemberWithUser} from "@/db/schema/03_organization"; import {useState} from "react"; import {authClient, useSession} from "@/lib/auth/auth-client"; import {useMutation} from "@tanstack/react-query"; diff --git a/src/components/wrappers/dashboard/settings/settings-organization-members-table.tsx b/src/components/wrappers/dashboard/settings/settings-organization-members-table.tsx index b233d347..ab878203 100644 --- a/src/components/wrappers/dashboard/settings/settings-organization-members-table.tsx +++ b/src/components/wrappers/dashboard/settings/settings-organization-members-table.tsx @@ -1,7 +1,7 @@ import {DataTable} from "@/components/wrappers/common/table/data-table"; import {usersColumnsAdmin} from "@/components/wrappers/dashboard/admin/columns-users"; -import {MemberWithUser, Organization, OrganizationWithMembers} from "@/db/schema/02_organization"; -import {OrganizationInvitation} from "@/db/schema/04_invitation"; +import {MemberWithUser, Organization, OrganizationWithMembers} from "@/db/schema/03_organization"; +import {OrganizationInvitation} from "@/db/schema/05_invitation"; import {organizationMemberColumns} from "@/components/wrappers/dashboard/settings/columns-organization-members"; diff --git a/src/db/index.ts b/src/db/index.ts index a9971a65..f927b9b7 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,13 +1,13 @@ import {drizzle} from "drizzle-orm/node-postgres"; -import * as settings from "./schema/00_setting"; -import * as user from "./schema/01_user"; -import * as organisation from "./schema/02_organization"; -import * as invitation from "./schema/03_member"; -import * as member from "./schema/04_invitation"; -import * as project from "./schema/05_project"; -import * as agent from "./schema/07_agent"; -import * as database from "./schema/06_database"; +import * as settings from "./schema/01_setting"; +import * as user from "./schema/02_user"; +import * as organisation from "./schema/03_organization"; +import * as invitation from "./schema/04_member"; +import * as member from "./schema/05_invitation"; +import * as project from "./schema/06_project"; +import * as agent from "./schema/08_agent"; +import * as database from "./schema/07_database"; import {Pool} from "pg"; diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json old mode 100755 new mode 100644 index 9e13801f..d227402b --- a/src/db/migrations/meta/_journal.json +++ b/src/db/migrations/meta/_journal.json @@ -29,6 +29,13 @@ "when": 1756321312852, "tag": "0003_absent_maestro", "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1756473637441, + "tag": "0004_dazzling_hawkeye", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/schema/00_common.ts b/src/db/schema/00_common.ts new file mode 100644 index 00000000..2a235a66 --- /dev/null +++ b/src/db/schema/00_common.ts @@ -0,0 +1,15 @@ +import {timestamp} from "drizzle-orm/pg-core"; +import {z} from "zod"; + +export const timestamps = { + updatedAt: timestamp("updated_at"), + createdAt: timestamp("created_at").defaultNow().notNull(), + deletedAt: timestamp("deleted_at"), +} + +export function schemaWithoutMeta< + T extends z.ZodTypeAny +>(schema: T) { + // @ts-ignore + return schema.omit({id: true, createdAt: true, updatedAt: true, deletedAt: true}); +} diff --git a/src/db/schema/00_setting.ts b/src/db/schema/01_setting.ts similarity index 91% rename from src/db/schema/00_setting.ts rename to src/db/schema/01_setting.ts index 9dc11af4..99cf6e75 100644 --- a/src/db/schema/00_setting.ts +++ b/src/db/schema/01_setting.ts @@ -2,6 +2,7 @@ import { pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core"; import { typeStorageEnum } from "./types"; import { createSelectSchema } from "drizzle-zod"; import { z } from "zod"; +import {timestamps} from "@/db/schema/00_common"; export const setting = pgTable("settings", { id: uuid("id").primaryKey().defaultRandom(), @@ -16,8 +17,7 @@ export const setting = pgTable("settings", { smtpHost: varchar("smtp_host", { length: 255 }), smtpPort: varchar("smtp_port", { length: 255 }), smtpUser: varchar("smtp_user", { length: 255 }), - createdAt: timestamp("created_at").notNull().defaultNow(), - updatedAt: timestamp("updated_at"), + ...timestamps }); export const settingSchema = createSelectSchema(setting); diff --git a/src/db/schema/01_user.ts b/src/db/schema/02_user.ts similarity index 65% rename from src/db/schema/01_user.ts rename to src/db/schema/02_user.ts index 4e366b0c..23dedec1 100644 --- a/src/db/schema/01_user.ts +++ b/src/db/schema/02_user.ts @@ -1,12 +1,13 @@ -import { relations } from "drizzle-orm"; -import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; -import { createSelectSchema } from "drizzle-zod"; -import { z } from "zod"; -import { project } from "./05_project"; -import {member, OrganizationMember} from "@/db/schema/03_member"; -import {invitation} from "@/db/schema/04_invitation"; -import {organization} from "@/db/schema/02_organization"; +import {relations} from "drizzle-orm"; +import {boolean, pgTable, text, timestamp, uuid} from "drizzle-orm/pg-core"; +import {createSelectSchema} from "drizzle-zod"; +import {z} from "zod"; +import {project} from "./06_project"; +import {member, OrganizationMember} from "@/db/schema/04_member"; +import {invitation} from "@/db/schema/05_invitation"; +import {organization} from "@/db/schema/03_organization"; import {Account} from "better-auth"; +import {timestamps} from "@/db/schema/00_common"; export const user = pgTable("user", { id: uuid("id").defaultRandom().primaryKey(), @@ -14,28 +15,26 @@ export const user = pgTable("user", { email: text("email").notNull().unique(), emailVerified: boolean("email_verified").notNull(), image: text("image"), - createdAt: timestamp("created_at").notNull().defaultNow(), - updatedAt: timestamp("updated_at"), role: text("role"), banned: boolean("banned"), banReason: text("ban_reason"), banExpires: timestamp("ban_expires"), - deletedAt: timestamp("deleted_at"), + ...timestamps }); export const session = pgTable("session", { id: uuid("id").defaultRandom().primaryKey(), expiresAt: timestamp("expires_at").notNull(), token: text("token").notNull().unique(), - createdAt: timestamp("created_at").notNull().defaultNow(), - updatedAt: timestamp("updated_at"), ipAddress: text("ip_address"), userAgent: text("user_agent"), userId: uuid("user_id") .notNull() - .references(() => user.id, { onDelete: "cascade" }), + .references(() => user.id, {onDelete: "cascade"}), impersonatedBy: text("impersonated_by"), //id or name ???? activeOrganizationId: text("active_organization_id"), + ...timestamps + }); export const account = pgTable("account", { @@ -44,7 +43,7 @@ export const account = pgTable("account", { providerId: text("provider_id").notNull(), userId: uuid("user_id") .notNull() - .references(() => user.id, { onDelete: "cascade" }), + .references(() => user.id, {onDelete: "cascade"}), accessToken: text("access_token"), refreshToken: text("refresh_token"), idToken: text("id_token"), @@ -52,8 +51,8 @@ export const account = pgTable("account", { refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), scope: text("scope"), password: text("password"), - createdAt: timestamp("created_at").notNull().defaultNow(), - updatedAt: timestamp("updated_at"), + ...timestamps + }); export const verification = pgTable("verification", { @@ -61,32 +60,32 @@ export const verification = pgTable("verification", { identifier: text("identifier").notNull(), value: text("value").notNull(), expiresAt: timestamp("expires_at").notNull(), - createdAt: timestamp("created_at").notNull().defaultNow(), - updatedAt: timestamp("updated_at"), + ...timestamps + }); -export const userRelations = relations(user, ({ many }) => ({ +export const userRelations = relations(user, ({many}) => ({ sessions: many(session), accounts: many(account), memberships: many(member), invitations: many(invitation), })); -export const sessionRelations = relations(session, ({ one }) => ({ +export const sessionRelations = relations(session, ({one}) => ({ user: one(user, { fields: [session.userId], references: [user.id], }), })); -export const accountRelations = relations(account, ({ one }) => ({ +export const accountRelations = relations(account, ({one}) => ({ user: one(user, { fields: [account.userId], references: [user.id], }), })); -export const projectRelations = relations(project, ({ one }) => ({ +export const projectRelations = relations(project, ({one}) => ({ organization: one(organization, { fields: [project.organizationId], references: [organization.id], diff --git a/src/db/schema/02_organization.ts b/src/db/schema/03_organization.ts similarity index 77% rename from src/db/schema/02_organization.ts rename to src/db/schema/03_organization.ts index cc6ad586..2197cef4 100644 --- a/src/db/schema/02_organization.ts +++ b/src/db/schema/03_organization.ts @@ -1,20 +1,20 @@ import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; import { relations } from "drizzle-orm"; -import { project } from "./05_project"; +import { project } from "./06_project"; import { createSelectSchema } from "drizzle-zod"; import { z } from "zod"; -import {invitation, OrganizationInvitation} from "@/db/schema/04_invitation"; -import {member, OrganizationMember} from "@/db/schema/03_member"; -import {User} from "@/db/schema/01_user"; +import {invitation, OrganizationInvitation} from "@/db/schema/05_invitation"; +import {member, OrganizationMember} from "@/db/schema/04_member"; +import {User} from "@/db/schema/02_user"; +import {timestamps} from "@/db/schema/00_common"; export const organization = pgTable("organization", { id: uuid("id").defaultRandom().primaryKey(), name: text("name").notNull(), slug: text("slug").unique().notNull(), logo: text("logo"), - createdAt: timestamp("created_at").notNull().defaultNow(), - updatedAt: timestamp("updated_at"), metadata: text("metadata"), + ...timestamps }); diff --git a/src/db/schema/03_member.ts b/src/db/schema/04_member.ts similarity index 82% rename from src/db/schema/03_member.ts rename to src/db/schema/04_member.ts index 6a945a85..36ff8d91 100644 --- a/src/db/schema/03_member.ts +++ b/src/db/schema/04_member.ts @@ -1,9 +1,10 @@ import {pgTable, text, timestamp, uuid} from "drizzle-orm/pg-core"; -import {user} from "@/db/schema/01_user"; -import {organization} from "@/db/schema/02_organization"; +import {user} from "@/db/schema/02_user"; +import {organization} from "@/db/schema/03_organization"; import {relations} from "drizzle-orm"; import {createSelectSchema} from "drizzle-zod"; import {z} from "zod"; +import {timestamps} from "@/db/schema/00_common"; export const member = pgTable("member", { @@ -15,8 +16,7 @@ export const member = pgTable("member", { .notNull() .references(() => user.id, { onDelete: "cascade" }), role: text("role").notNull(), - createdAt: timestamp("created_at").notNull().defaultNow(), - updatedAt: timestamp("updated_at"), + ...timestamps }); export const memberRelations = relations(member, ({ one }) => ({ diff --git a/src/db/schema/04_invitation.ts b/src/db/schema/05_invitation.ts similarity index 87% rename from src/db/schema/04_invitation.ts rename to src/db/schema/05_invitation.ts index 353f8cdc..612207a0 100644 --- a/src/db/schema/04_invitation.ts +++ b/src/db/schema/05_invitation.ts @@ -1,9 +1,10 @@ import {relations} from "drizzle-orm"; -import {user} from "@/db/schema/01_user"; -import {organization} from "@/db/schema/02_organization"; +import {user} from "@/db/schema/02_user"; +import {organization} from "@/db/schema/03_organization"; import {createSelectSchema} from "drizzle-zod"; import {z} from "zod"; import {pgTable, text, timestamp, uuid} from "drizzle-orm/pg-core"; +import {timestamps} from "@/db/schema/00_common"; export const invitation = pgTable("invitation", { @@ -18,6 +19,7 @@ export const invitation = pgTable("invitation", { inviterId: uuid("inviter_id") .notNull() .references(() => user.id, { onDelete: "cascade" }), + ...timestamps }); diff --git a/src/db/schema/05_project.ts b/src/db/schema/06_project.ts similarity index 81% rename from src/db/schema/05_project.ts rename to src/db/schema/06_project.ts index 93a04ba0..6e1ccdb9 100644 --- a/src/db/schema/05_project.ts +++ b/src/db/schema/06_project.ts @@ -1,20 +1,21 @@ import { pgTable, text, boolean, uuid, timestamp } from "drizzle-orm/pg-core"; import { relations } from "drizzle-orm"; -import { Organization, organization } from "./02_organization"; +import { Organization, organization } from "./03_organization"; import { createSelectSchema } from "drizzle-zod"; import { z } from "zod"; -import { Database, database } from "./06_database"; +import { Database, database } from "./07_database"; +import {timestamps} from "@/db/schema/00_common"; export const project = pgTable("projects", { id: uuid("id").primaryKey().defaultRandom(), slug: text("slug").notNull().unique(), name: text("name").notNull().notNull(), isArchived: boolean("is_archived").default(false), - createdAt: timestamp("created_at").notNull().defaultNow(), - updatedAt: timestamp("updated_at"), organizationId: uuid("organization_id") .notNull() .references(() => organization.id), + ...timestamps + }); export const projectRelations = relations(project, ({ one, many }) => ({ diff --git a/src/db/schema/06_database.ts b/src/db/schema/07_database.ts similarity index 88% rename from src/db/schema/06_database.ts rename to src/db/schema/07_database.ts index 4fa0ad4c..aaeceb42 100644 --- a/src/db/schema/06_database.ts +++ b/src/db/schema/07_database.ts @@ -1,10 +1,11 @@ import {pgTable, text, boolean, timestamp, uuid, integer, pgEnum, uniqueIndex} from "drizzle-orm/pg-core"; -import {Agent, agent} from "./07_agent"; -import {Project, project} from "./05_project"; +import {Agent, agent} from "./08_agent"; +import {Project, project} from "./06_project"; import {relations} from "drizzle-orm"; import {dbmsEnum, statusEnum} from "./types"; import {createSelectSchema} from "drizzle-zod"; import {z} from "zod"; +import {timestamps} from "@/db/schema/00_common"; export const database = pgTable("databases", { id: uuid("id").primaryKey().defaultRandom(), @@ -15,8 +16,6 @@ export const database = pgTable("databases", { backupPolicy: text("backup_policy"), isWaitingForBackup: boolean("is_waiting_for_backup").default(false).notNull(), backupToRestore: text("backup_to_restore"), - createdAt: timestamp("created_at").notNull().defaultNow(), - updatedAt: timestamp("updated_at"), agentId: uuid("agent_id") .notNull() .references(() => agent.id, {onDelete: "cascade"}), @@ -24,6 +23,7 @@ export const database = pgTable("databases", { projectId: uuid("project_id") .references(() => project.id), + ...timestamps }); @@ -33,11 +33,10 @@ export const backup = pgTable( id: uuid("id").primaryKey().defaultRandom(), status: statusEnum("status").default("waiting").notNull(), file: text("file"), - createdAt: timestamp("created_at").notNull().defaultNow(), - updatedAt: timestamp("updated_at"), databaseId: uuid("database_id") .notNull() .references(() => database.id, {onDelete: "cascade"}), + ...timestamps }, // (table) => [uniqueIndex("database_id_status_unique").on(table.databaseId, table.status)] ); @@ -54,20 +53,19 @@ export const retentionPolicy = pgTable("retention_policies", { gfsWeekly: integer("gfs_weekly").default(4), gfsMonthly: integer("gfs_monthly").default(12), gfsYearly: integer("gfs_yearly").default(3), - createdAt: timestamp("created_at").notNull().defaultNow(), - updatedAt: timestamp("updated_at"), + ...timestamps }); export const restoration = pgTable("restorations", { id: uuid("id").primaryKey().defaultRandom(), status: statusEnum("status").default("waiting").notNull(), - createdAt: timestamp("created_at").notNull().defaultNow(), - updatedAt: timestamp("updated_at"), backupId: uuid("backup_id") .notNull() .references(() => backup.id, {onDelete: "cascade"}), databaseId: uuid("database_id").references(() => database.id, {onDelete: "cascade"}), + ...timestamps + }); export const databaseRelations = relations(database, ({one, many}) => ({ @@ -107,6 +105,5 @@ export type DatabaseWith = Database & { backups?: Backup[] | null; restorations?: Restoration[] | null; retentionPolicy?: RetentionPolicy | null; - }; diff --git a/src/db/schema/07_agent.ts b/src/db/schema/08_agent.ts similarity index 82% rename from src/db/schema/07_agent.ts rename to src/db/schema/08_agent.ts index 25c3ae37..4fcce4ca 100644 --- a/src/db/schema/07_agent.ts +++ b/src/db/schema/08_agent.ts @@ -1,8 +1,9 @@ import {boolean, pgTable, text, timestamp, uuid} from "drizzle-orm/pg-core"; import { createSelectSchema } from "drizzle-zod"; import { z } from "zod"; -import {database} from "@/db/schema/06_database"; +import {database} from "@/db/schema/07_database"; import {relations} from "drizzle-orm"; +import {timestamps} from "@/db/schema/00_common"; export const agent = pgTable("agents", { id: uuid("id").primaryKey().defaultRandom(), @@ -10,9 +11,9 @@ export const agent = pgTable("agents", { name: text("name").notNull().notNull(), description: text("description").notNull(), isArchived: boolean("is_archived").default(false), - createdAt: timestamp("created_at").notNull().defaultNow(), - updatedAt: timestamp("updated_at"), lastContact: timestamp("last_contact"), + ...timestamps + }); export const agentSchema = createSelectSchema(agent); diff --git a/src/db/utils/index.ts b/src/db/utils/index.ts new file mode 100644 index 00000000..e37bc068 --- /dev/null +++ b/src/db/utils/index.ts @@ -0,0 +1,3 @@ +export function withUpdatedAt(data: T): T & { updatedAt: Date } { + return {...data, updatedAt: new Date()}; +} \ No newline at end of file diff --git a/src/features/dashboard/backup/columns.tsx b/src/features/dashboard/backup/columns.tsx index ffc7f3b5..48c0b446 100644 --- a/src/features/dashboard/backup/columns.tsx +++ b/src/features/dashboard/backup/columns.tsx @@ -21,10 +21,10 @@ import {createRestorationAction, deleteBackupAction} from "@/features/dashboard/ import {toast} from "sonner"; import {useRouter} from "next/navigation"; import {StatusBadge} from "@/components/wrappers/common/status-badge"; -import {Backup, DatabaseWith} from "@/db/schema/06_database"; +import {Backup, DatabaseWith} from "@/db/schema/07_database"; import {formatFrenchDate} from "@/utils/date-formatting"; import {TooltipCustom} from "@/components/wrappers/common/tooltip-custom"; -import {Setting} from "@/db/schema/00_setting"; +import {Setting} from "@/db/schema/01_setting"; import {SafeActionResult} from "next-safe-action"; import {ZodString} from "zod"; import {ServerActionResult} from "@/types/action-type"; @@ -80,18 +80,22 @@ export function backupColumns(isAlreadyRestore: boolean, settings: Setting, data const mutationDeleteBackup = useMutation({ mutationFn: async () => { - const restoration = await deleteBackupAction({ + + + const deletion = await deleteBackupAction({ backupId: rowData.id, databaseId: rowData.databaseId, + file: rowData.file!, + projectSlug: database.project?.slug! }); // @ts-ignore - if (restoration.data.success) { + if (deletion.data.success) { // @ts-ignore - toast.success(restoration.data.actionSuccess.message); + toast.success(deletion.data.actionSuccess.message); router.refresh(); } else { // @ts-ignore - toast.error(restoration.data.actionError.message); + toast.error(deletion.data.actionError.message); } }, }); diff --git a/src/features/dashboard/restore/columns.tsx b/src/features/dashboard/restore/columns.tsx index aa841161..0c0f0951 100644 --- a/src/features/dashboard/restore/columns.tsx +++ b/src/features/dashboard/restore/columns.tsx @@ -13,7 +13,7 @@ import { Button } from "@/components/ui/button"; import {MoreHorizontal, Trash2} from "lucide-react"; import { ReloadIcon } from "@radix-ui/react-icons"; import { StatusBadge } from "@/components/wrappers/common/status-badge"; -import {Backup, Restoration} from "@/db/schema/06_database"; +import {Backup, Restoration} from "@/db/schema/07_database"; import {formatFrenchDate} from "@/utils/date-formatting"; import {useMutation} from "@tanstack/react-query"; import { diff --git a/src/features/dashboard/restore/restore.action.ts b/src/features/dashboard/restore/restore.action.ts index 61048ccd..7f6c6453 100644 --- a/src/features/dashboard/restore/restore.action.ts +++ b/src/features/dashboard/restore/restore.action.ts @@ -1,12 +1,20 @@ "use server" -import { userAction } from "@/safe-actions"; -import { z } from "zod"; -import { ServerActionResult } from "@/types/action-type"; +import {userAction} from "@/safe-actions"; +import {z} from "zod"; +import {ServerActionResult} from "@/types/action-type"; import * as drizzleDb from "@/db"; -import { db } from "@/db"; -import { and, eq } from "drizzle-orm"; -import {Backup, Restoration} from "@/db/schema/06_database"; +import {db} from "@/db"; +import {and, eq} from "drizzle-orm"; +import {Backup, Restoration} from "@/db/schema/07_database"; +import {NextResponse} from "next/server"; +import { + deleteFileS3Private, + deleteLocalPrivate, + uploadLocalPrivate, + uploadS3Private +} from "@/features/upload/private/upload.action"; +import {env} from "@/env.mjs"; export const deleteRestoreAction = userAction .schema( @@ -14,7 +22,7 @@ export const deleteRestoreAction = userAction restorationId: z.string(), }) ) - .action(async ({ parsedInput }): Promise> => { + .action(async ({parsedInput}): Promise> => { try { await db .delete(drizzleDb.schemas.restoration) @@ -36,24 +44,60 @@ export const deleteRestoreAction = userAction message: "Failed to delete restoration.", status: 500, cause: error instanceof Error ? error.message : "Unknown error", - messageParams: { message: "Error deleting the restoration" }, + messageParams: {message: "Error deleting the restoration"}, }, }; } }); - - export const deleteBackupAction = userAction .schema( z.object({ backupId: z.string(), databaseId: z.string(), + projectSlug: z.string(), + file: z.string(), }) ) - .action(async ({ parsedInput }): Promise> => { + .action(async ({parsedInput}): Promise> => { try { + + const [settings] = await db.select().from(drizzleDb.schemas.setting).where(eq(drizzleDb.schemas.setting.name, "system")).limit(1); + if (!settings) { + return { + success: false, + actionError: { + message: "No settings found.", + status: 404, + cause: "No settings found.", + messageParams: {message: "Error deleting the backup"}, + }, + }; + } + + + let success: boolean, message: string; + + const result = + settings.storage === "local" + ? await deleteLocalPrivate(parsedInput.file) + : await deleteFileS3Private(`${parsedInput.projectSlug}/${parsedInput.file}`, env.S3_BUCKET_NAME!); + + ({success, message} = result); + + if (!success) { + return { + success: false, + actionError: { + message: message, + status: 404, + cause: "Unable to delete backup from storage", + messageParams: {message: "Error deleting the backup"}, + }, + }; + } + await db .delete(drizzleDb.schemas.backup) .where(and(eq(drizzleDb.schemas.backup.id, parsedInput.backupId), eq(drizzleDb.schemas.backup.databaseId, parsedInput.databaseId))) @@ -65,6 +109,7 @@ export const deleteBackupAction = userAction .where(and(eq(drizzleDb.schemas.backup.id, parsedInput.backupId), eq(drizzleDb.schemas.backup.databaseId, parsedInput.databaseId))) .execute(); + if (backupExists.length === 0) { return { success: true, @@ -78,8 +123,8 @@ export const deleteBackupAction = userAction actionError: { message: "Backup not found or already deleted.", status: 404, - cause: "Backup could not be deleted.", - messageParams: { message: "Error deleting the backup" }, + cause: "Backup could not be deleted (from database or remote storage).", + messageParams: {message: "Error deleting the backup"}, }, }; } @@ -90,25 +135,24 @@ export const deleteBackupAction = userAction message: "Failed to delete backup.", status: 500, cause: error instanceof Error ? error.message : "Unknown error", - messageParams: { message: "Error deleting the backup" }, + messageParams: {message: "Error deleting the backup"}, }, }; } }); - export const rerunRestorationAction = userAction .schema( z.object({ restorationId: z.string(), }) ) - .action(async ({ parsedInput }): Promise> => { + .action(async ({parsedInput}): Promise> => { try { const updateResult = await db .update(drizzleDb.schemas.restoration) - .set({ status: "waiting" }) + .set({status: "waiting"}) .where(eq(drizzleDb.schemas.restoration.id, parsedInput.restorationId)) .returning() .execute(); @@ -122,7 +166,7 @@ export const rerunRestorationAction = userAction message: "Restoration not found.", status: 404, cause: "No restoration with the given ID exists.", - messageParams: { message: "Restoration not found" }, + messageParams: {message: "Restoration not found"}, }, }; } @@ -132,7 +176,7 @@ export const rerunRestorationAction = userAction value: updatedRestoration, actionSuccess: { message: "Restoration has been requeued.", - messageParams: { restorationId: updatedRestoration.id }, + messageParams: {restorationId: updatedRestoration.id}, }, }; } catch (error) { @@ -142,7 +186,7 @@ export const rerunRestorationAction = userAction message: "Failed to rerun restoration.", status: 500, cause: error instanceof Error ? error.message : "Unknown error", - messageParams: { message: "Error updating the restoration" }, + messageParams: {message: "Error updating the restoration"}, }, }; } @@ -157,7 +201,7 @@ export const createRestorationAction = userAction databaseId: z.string(), }) ) - .action(async ({ parsedInput }): Promise> => { + .action(async ({parsedInput}): Promise> => { try { // Insert new restoration into the database const restorationData = await db @@ -177,7 +221,7 @@ export const createRestorationAction = userAction value: createdRestoration, actionSuccess: { message: "Restoration has been successfully created.", - messageParams: { restorationId: createdRestoration.id }, + messageParams: {restorationId: createdRestoration.id}, }, }; } catch (error) { @@ -187,7 +231,7 @@ export const createRestorationAction = userAction message: "Failed to create restoration.", status: 500, cause: error instanceof Error ? error.message : "Unknown error", - messageParams: { message: "Error creating the restoration" }, + messageParams: {message: "Error creating the restoration"}, }, }; } diff --git a/src/features/upload/private/upload.action.ts b/src/features/upload/private/upload.action.ts index 70cefea5..00a8130f 100644 --- a/src/features/upload/private/upload.action.ts +++ b/src/features/upload/private/upload.action.ts @@ -4,15 +4,12 @@ import {mkdir, writeFile} from "fs/promises"; import path from "path"; import * as fs from "node:fs"; import {getServerUrl} from "@/utils/get-server-url"; -import {createPresignedUrlToDownload, saveFileInBucket} from "@/utils/s3-file-management"; -import crypto from "crypto"; +import {createPresignedUrlToDownload, deleteFileFromBucket, saveFileInBucket} from "@/utils/s3-file-management"; import {env} from "@/env.mjs"; -import {action, userAction} from "@/safe-actions"; +import {action} from "@/safe-actions"; import {z} from "zod"; import {ServerActionResult} from "@/types/action-type"; -import {Backup} from "@/db/schema/06_database"; -import {db} from "@/db"; -import * as drizzleDb from "@/db"; +import {unlink} from "fs/promises"; const privateLocalDir = "private/uploads/files/"; const privateS3Dir = "backups/"; @@ -54,6 +51,53 @@ export async function uploadS3Private(fileName: string, buffer: any, bucketName: } +export async function deleteFileS3Private(fileName: string, bucketName: string) { + try { + + await deleteFileFromBucket({ + bucketName, + fileName: `${privateS3Dir}${fileName}`, + }); + + return { + success: true, + message: "File deleted successfully", + }; + } catch (error) { + console.error("Error occurred:", error); + throw new Error("An error occurred while deleting the private file"); + } +} + + +/** + * Delete a file from local private storage + */ +export async function deleteLocalPrivate(fileName: string) { + try { + const filePath = path.join(process.cwd(), privateLocalDir, fileName); + + // Delete locally + await unlink(filePath); + + return { + success: true, + message: `File '${fileName}' deleted successfully`, + }; + } catch (error: any) { + if (error.code === "ENOENT") { + return { + success: false, + message: `File '${fileName}' not found`, + }; + } + + console.error("Error occurred while deleting file:", error); + throw new Error("An error occurred while deleting the file"); + } +} + + // export async function getFileUrlPresignedLocal(fileName: string) { // try { // const filePath = path.join(privateLocalDir, fileName); diff --git a/src/features/upload/public/upload.action.ts b/src/features/upload/public/upload.action.ts index 8d2150a8..e1a1901e 100644 --- a/src/features/upload/public/upload.action.ts +++ b/src/features/upload/public/upload.action.ts @@ -11,7 +11,7 @@ import { UploadedObjectInfo } from "minio/src/internal/type"; import { getServerUrl } from "@/utils/get-server-url"; import { db } from "@/db"; import { eq } from "drizzle-orm"; -import {Setting} from "@/db/schema/00_setting"; +import {Setting} from "@/db/schema/01_setting"; import * as drizzleDb from "@/db"; export const uploadImageAction = userAction.schema(z.instanceof(FormData)).action(async ({ parsedInput: formData, ctx }) => { diff --git a/src/lib/auth/auth.ts b/src/lib/auth/auth.ts index cb886cb9..1e46c6fc 100644 --- a/src/lib/auth/auth.ts +++ b/src/lib/auth/auth.ts @@ -8,7 +8,7 @@ import {admin as adminPlugin, openAPI, Organization, organization} from "better- import {ac, admin, orgAdmin, orgMember, orgOwner, pending, superadmin, user} from "@/lib/auth/permissions"; import {headers} from "next/headers"; import {count, eq} from "drizzle-orm"; -import {OrganizationWithMembers, OrganizationWithMembersAndUsers} from "@/db/schema/02_organization"; +import {OrganizationWithMembers, OrganizationWithMembersAndUsers} from "@/db/schema/03_organization"; export const auth = betterAuth({ database: drizzleAdapter(db, { diff --git a/src/lib/tasks/database/index.ts b/src/lib/tasks/database/index.ts index 7a56637e..f0b0881d 100644 --- a/src/lib/tasks/database/index.ts +++ b/src/lib/tasks/database/index.ts @@ -2,7 +2,7 @@ import {db} from "@/db"; import {enforceRetentionCount} from "@/lib/tasks/database/retention-count"; import {enforceRetentionDays} from "@/lib/tasks/database/retention-days"; import {enforceRetentionGFS} from "@/lib/tasks/database/retention-gsf"; -import {retentionPolicy} from "@/db/schema/06_database"; +import {retentionPolicy} from "@/db/schema/07_database"; export const retentionCleanTask = async () => { diff --git a/src/utils/s3-file-management.ts b/src/utils/s3-file-management.ts index 986fa559..ceb7bc28 100644 --- a/src/utils/s3-file-management.ts +++ b/src/utils/s3-file-management.ts @@ -68,6 +68,46 @@ export async function createBucketIfNotExists(bucketName: string) { } } +/** + * Delete a file from a bucket + * @param bucketName name of the bucket + * @param fileName name of the file + * @returns true if deleted, false if not + */ +export async function deleteFileFromBucket({ + bucketName, + fileName, + }: { + bucketName: string; + fileName: string; +}): Promise { + const s3Client = await getS3Client(); + + try { + const fileExists = await checkFileExistsInBucket({ bucketName, fileName }); + + if (!fileExists) { + console.warn(`File not found: ${bucketName}/${fileName}`); + return false; + } + + await s3Client.removeObject(bucketName, fileName); + console.log(`Deleted file: ${bucketName}/${fileName}`); + return true; + } catch (error: any) { + console.error("Error deleting file from bucket:", { + bucketName, + fileName, + error: error.message, + }); + return false; + } +} + + + + + /** * Save file in S3 bucket * @param bucketName name of the bucket