From b9d9f42ec7b26af15b44ce2a78077bc8df2a4dac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20LAGACHE?= Date: Tue, 23 Jun 2026 16:06:07 +0200 Subject: [PATCH] refactor(agents): reorganize into actions/components/schemas/hooks --- .../(admin)/agents/[agentId]/page.tsx | 6 +- .../dashboard/(admin)/agents/page.tsx | 4 +- .../settings/agents/[agentId]/page.tsx | 6 +- app/api/v1/agents/[id]/route.ts | 2 +- app/api/v1/agents/route.ts | 2 +- .../{ => actions}/agent-delete.action.ts | 0 .../agent-organizations.action.ts | 0 .../agents/{ => actions}/agents.action.ts | 2 +- .../agents/{ => actions}/keys.action.ts | 0 .../{ => components}/agent-card-key.tsx | 0 .../agents/{ => components}/agent-card.tsx | 2 +- .../agents/{ => components}/agent-content.tsx | 6 +- .../{ => components}/agent-database-card.tsx | 0 .../agent-database-columns.tsx | 0 .../{ => components}/agent-delete-button.tsx | 2 +- .../agents/{ => components}/agent-dialog.tsx | 4 +- .../agents/{ => components}/agent-form.tsx | 4 +- .../{ => components}/agent-key-modal.tsx | 0 .../agent-organizations.form.tsx | 4 +- .../{ => hooks}/use-agent-update-check.ts | 0 .../agent-organizations.schema.ts | 0 .../agents/{ => schemas}/agents.schema.ts | 0 .../actions/get-agent-status.action.ts | 2 +- .../onboarding/hooks/use-create-agent.ts | 2 +- .../onboarding/hooks/use-delete-agent.ts | 2 +- .../onboarding/steps/step-agent-key.tsx | 2 +- .../organizations/organization-agents-tab.tsx | 4 +- src/features/settings/storage-section.tsx | 2 +- src/lib/api-v1/openapi/routes/agents.ts | 2 +- src/lib/auth/current-user.ts | 16 +- src/lib/auth/passkey-context.ts | 34 ++-- src/lib/auth/permissions.ts | 1 - src/lib/email/helpers.ts | 26 +-- src/lib/email/index.ts | 71 ++++---- src/lib/email/types.ts | 22 ++- src/lib/mcp/http-client.ts | 11 +- src/lib/mcp/server.ts | 2 +- src/lib/mcp/tools/agents.ts | 42 +++-- src/lib/mcp/tools/backups.ts | 21 ++- src/lib/mcp/tools/databases.ts | 79 +++++---- src/lib/mcp/tools/response.ts | 16 +- src/lib/safe-actions/actions.ts | 36 ++-- src/lib/tasks/database/retention-days.ts | 74 +++++---- src/lib/tasks/database/retention-gsf.ts | 155 ++++++++++-------- src/utils/edge_key.ts | 2 +- 45 files changed, 361 insertions(+), 307 deletions(-) rename src/features/agents/{ => actions}/agent-delete.action.ts (100%) rename src/features/agents/{ => actions}/agent-organizations.action.ts (100%) rename src/features/agents/{ => actions}/agents.action.ts (97%) rename src/features/agents/{ => actions}/keys.action.ts (100%) rename src/features/agents/{ => components}/agent-card-key.tsx (100%) rename src/features/agents/{ => components}/agent-card.tsx (98%) rename src/features/agents/{ => components}/agent-content.tsx (96%) rename src/features/agents/{ => components}/agent-database-card.tsx (100%) rename src/features/agents/{ => components}/agent-database-columns.tsx (100%) rename src/features/agents/{ => components}/agent-delete-button.tsx (96%) rename src/features/agents/{ => components}/agent-dialog.tsx (96%) rename src/features/agents/{ => components}/agent-form.tsx (97%) rename src/features/agents/{ => components}/agent-key-modal.tsx (100%) rename src/features/agents/{ => components}/agent-organizations.form.tsx (97%) rename src/features/agents/{ => hooks}/use-agent-update-check.ts (100%) rename src/features/agents/{ => schemas}/agent-organizations.schema.ts (100%) rename src/features/agents/{ => schemas}/agents.schema.ts (100%) diff --git a/app/(customer)/dashboard/(admin)/agents/[agentId]/page.tsx b/app/(customer)/dashboard/(admin)/agents/[agentId]/page.tsx index 8b324e03..00a45dfa 100644 --- a/app/(customer)/dashboard/(admin)/agents/[agentId]/page.tsx +++ b/app/(customer)/dashboard/(admin)/agents/[agentId]/page.tsx @@ -9,12 +9,12 @@ import { db } from "@/db"; import * as drizzleDb from "@/db"; import {eq, isNull} from "drizzle-orm"; import { notFound } from "next/navigation"; -import { ButtonDeleteAgent } from "@/features/agents/agent-delete-button"; +import { ButtonDeleteAgent } from "@/features/agents/components/agent-delete-button"; import { capitalizeFirstLetter } from "@/utils/text"; import { generateEdgeKey } from "@/utils/edge_key"; import { getServerUrl } from "@/utils/get-server-url"; -import { AgentContentPage } from "@/features/agents/agent-content"; -import { AgentDialog } from "@/features/agents/agent-dialog"; +import { AgentContentPage } from "@/features/agents/components/agent-content"; +import { AgentDialog } from "@/features/agents/components/agent-dialog"; export default async function RoutePage( diff --git a/app/(customer)/dashboard/(admin)/agents/page.tsx b/app/(customer)/dashboard/(admin)/agents/page.tsx index eab14b56..5ee04b4e 100644 --- a/app/(customer)/dashboard/(admin)/agents/page.tsx +++ b/app/(customer)/dashboard/(admin)/agents/page.tsx @@ -1,5 +1,5 @@ import {PageParams} from "@/types/next"; -import {AgentCard} from "@/features/agents/agent-card"; +import {AgentCard} from "@/features/agents/components/agent-card"; import {CardsWithPagination} from "@/components/common/cards-with-pagination"; import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page"; import {notFound} from "next/navigation"; @@ -7,7 +7,7 @@ import {db} from "@/db"; import * as drizzleDb from "@/db"; import {and, desc, eq, isNull, not} from "drizzle-orm"; import {Metadata} from "next"; -import {AgentDialog} from "@/features/agents/agent-dialog"; +import {AgentDialog} from "@/features/agents/components/agent-dialog"; export const metadata: Metadata = { title: "Agents", diff --git a/app/(customer)/dashboard/(organization)/settings/agents/[agentId]/page.tsx b/app/(customer)/dashboard/(organization)/settings/agents/[agentId]/page.tsx index ddfe2207..c6e3a3d0 100644 --- a/app/(customer)/dashboard/(organization)/settings/agents/[agentId]/page.tsx +++ b/app/(customer)/dashboard/(organization)/settings/agents/[agentId]/page.tsx @@ -9,12 +9,12 @@ import {db} from "@/db"; import * as drizzleDb from "@/db"; import {eq} from "drizzle-orm"; import {notFound} from "next/navigation"; -import {ButtonDeleteAgent} from "@/features/agents/agent-delete-button"; +import {ButtonDeleteAgent} from "@/features/agents/components/agent-delete-button"; import {capitalizeFirstLetter} from "@/utils/text"; import {generateEdgeKey} from "@/utils/edge_key"; import {getServerUrl} from "@/utils/get-server-url"; -import {AgentContentPage} from "@/features/agents/agent-content"; -import {AgentDialog} from "@/features/agents/agent-dialog"; +import {AgentContentPage} from "@/features/agents/components/agent-content"; +import {AgentDialog} from "@/features/agents/components/agent-dialog"; import {getActiveMember, getOrganization} from "@/lib/auth/auth"; import {currentUser} from "@/lib/auth/current-user"; import {computeOrganizationPermissions} from "@/lib/acl/organization-acl"; diff --git a/app/api/v1/agents/[id]/route.ts b/app/api/v1/agents/[id]/route.ts index e8852efb..5c477b33 100644 --- a/app/api/v1/agents/[id]/route.ts +++ b/app/api/v1/agents/[id]/route.ts @@ -3,7 +3,7 @@ import {withApiKey} from "@/lib/api-v1/middleware"; import {logger} from "@/lib/logger"; import {ApiKeyContext} from "@/lib/api-v1/types"; import {getAgent, resolveAgentAccess} from "@/lib/api-v1/services/agents"; -import {deleteAgentService} from "@/features/agents/agent-delete.action"; +import {deleteAgentService} from "@/features/agents/actions/agent-delete.action"; const log = logger.child({module: "api/v1/agents/[id]"}); diff --git a/app/api/v1/agents/route.ts b/app/api/v1/agents/route.ts index f266532b..0245e455 100644 --- a/app/api/v1/agents/route.ts +++ b/app/api/v1/agents/route.ts @@ -5,7 +5,7 @@ import * as drizzleDb from "@/db"; import { inArray, eq, and, or, isNull } from "drizzle-orm"; import { z } from "zod"; import { logger } from "@/lib/logger"; -import {createAgentService} from "@/features/agents/agents.action"; +import {createAgentService} from "@/features/agents/actions/agents.action"; import { ActionError } from "@/lib/safe-actions/actions"; import {getAccessibleAgentIds} from "@/lib/api-v1/services/agents"; import {ApiKeyContext} from "@/lib/api-v1/types"; diff --git a/src/features/agents/agent-delete.action.ts b/src/features/agents/actions/agent-delete.action.ts similarity index 100% rename from src/features/agents/agent-delete.action.ts rename to src/features/agents/actions/agent-delete.action.ts diff --git a/src/features/agents/agent-organizations.action.ts b/src/features/agents/actions/agent-organizations.action.ts similarity index 100% rename from src/features/agents/agent-organizations.action.ts rename to src/features/agents/actions/agent-organizations.action.ts diff --git a/src/features/agents/agents.action.ts b/src/features/agents/actions/agents.action.ts similarity index 97% rename from src/features/agents/agents.action.ts rename to src/features/agents/actions/agents.action.ts index e22dbce4..d3d488c1 100644 --- a/src/features/agents/agents.action.ts +++ b/src/features/agents/actions/agents.action.ts @@ -1,6 +1,6 @@ "use server"; import {action, ActionError, userAction} from "@/lib/safe-actions/actions"; -import {AgentSchema} from "@/features/agents/agents.schema"; +import {AgentSchema} from "@/features/agents/schemas/agents.schema"; import {z} from "zod"; import {eq, and, ne, count, desc} from "drizzle-orm"; import {db} from "@/db"; diff --git a/src/features/agents/keys.action.ts b/src/features/agents/actions/keys.action.ts similarity index 100% rename from src/features/agents/keys.action.ts rename to src/features/agents/actions/keys.action.ts diff --git a/src/features/agents/agent-card-key.tsx b/src/features/agents/components/agent-card-key.tsx similarity index 100% rename from src/features/agents/agent-card-key.tsx rename to src/features/agents/components/agent-card-key.tsx diff --git a/src/features/agents/agent-card.tsx b/src/features/agents/components/agent-card.tsx similarity index 98% rename from src/features/agents/agent-card.tsx rename to src/features/agents/components/agent-card.tsx index 3b1f5277..6f556ef0 100644 --- a/src/features/agents/agent-card.tsx +++ b/src/features/agents/components/agent-card.tsx @@ -11,7 +11,7 @@ import {Badge} from "@/components/ui/badge"; import {truncateWords} from "@/utils/text"; import {useIsMobile} from "@/hooks/use-mobile"; import {Tooltip, TooltipContent, TooltipTrigger} from "@/components/ui/tooltip"; -import {useAgentUpdateCheck} from "@/features/agents/use-agent-update-check"; +import {useAgentUpdateCheck} from "@/features/agents/hooks/use-agent-update-check"; export type agentCardProps = { data: AgentWith; diff --git a/src/features/agents/agent-content.tsx b/src/features/agents/components/agent-content.tsx similarity index 96% rename from src/features/agents/agent-content.tsx rename to src/features/agents/components/agent-content.tsx index 6e6fe392..8be2b99b 100644 --- a/src/features/agents/agent-content.tsx +++ b/src/features/agents/components/agent-content.tsx @@ -3,10 +3,10 @@ import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"; import {Server} from "lucide-react"; import {formatDateLastContact} from "@/utils/date-formatting"; -import {AgentCardKey} from "@/features/agents/agent-card-key"; +import {AgentCardKey} from "@/features/agents/components/agent-card-key"; import {AgentWithDatabases} from "@/db/schema/08_agent"; import {useQuery} from "@tanstack/react-query"; -import {getAgentAction} from "@/features/agents/agents.action"; +import {getAgentAction} from "@/features/agents/actions/agents.action"; import { Accordion, AccordionContent, @@ -16,7 +16,7 @@ import { import {Separator} from "@/components/ui/separator"; import {Badge} from "@/components/ui/badge"; import {CardsWithPagination} from "@/components/common/cards-with-pagination"; -import {AgentDatabaseCard} from "@/features/agents/agent-database-card"; +import {AgentDatabaseCard} from "@/features/agents/components/agent-database-card"; import {HealthCheckGraph} from "@/features/database/health-grid"; import {HealthcheckLog} from "@/db/schema/15_healthcheck-log"; diff --git a/src/features/agents/agent-database-card.tsx b/src/features/agents/components/agent-database-card.tsx similarity index 100% rename from src/features/agents/agent-database-card.tsx rename to src/features/agents/components/agent-database-card.tsx diff --git a/src/features/agents/agent-database-columns.tsx b/src/features/agents/components/agent-database-columns.tsx similarity index 100% rename from src/features/agents/agent-database-columns.tsx rename to src/features/agents/components/agent-database-columns.tsx diff --git a/src/features/agents/agent-delete-button.tsx b/src/features/agents/components/agent-delete-button.tsx similarity index 96% rename from src/features/agents/agent-delete-button.tsx rename to src/features/agents/components/agent-delete-button.tsx index d7561f89..174aa325 100644 --- a/src/features/agents/agent-delete-button.tsx +++ b/src/features/agents/components/agent-delete-button.tsx @@ -5,7 +5,7 @@ import {ButtonWithConfirm} from "@/components/common/button-with-confirm"; import {useMutation} from "@tanstack/react-query"; import {useRouter} from "next/navigation"; import {toast} from "sonner"; -import {deleteAgentAction} from "@/features/agents/agent-delete.action"; +import {deleteAgentAction} from "@/features/agents/actions/agent-delete.action"; import {useIsMobile} from "@/hooks/use-mobile"; export type ButtonDeleteAgentProps = { diff --git a/src/features/agents/agent-dialog.tsx b/src/features/agents/components/agent-dialog.tsx similarity index 96% rename from src/features/agents/agent-dialog.tsx rename to src/features/agents/components/agent-dialog.tsx index 343d5117..9f7bbaf3 100644 --- a/src/features/agents/agent-dialog.tsx +++ b/src/features/agents/components/agent-dialog.tsx @@ -7,7 +7,7 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; -import {AgentForm} from "@/features/agents/agent-form"; +import {AgentForm} from "@/features/agents/components/agent-form"; import {Button, buttonVariants} from "@/components/ui/button"; import {Plus} from "lucide-react"; import {GearIcon} from "@radix-ui/react-icons"; @@ -16,7 +16,7 @@ import {useState} from "react"; import {useRouter} from "next/navigation"; import {OrganizationWithMembers} from "@/db/schema/03_organization"; import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs"; -import {AgentOrganisationForm} from "@/features/agents/agent-organizations.form"; +import {AgentOrganisationForm} from "@/features/agents/components/agent-organizations.form"; import {AgentWith} from "@/db/schema/08_agent"; type AgentDialogProps = { diff --git a/src/features/agents/agent-form.tsx b/src/features/agents/components/agent-form.tsx similarity index 97% rename from src/features/agents/agent-form.tsx rename to src/features/agents/components/agent-form.tsx index e9abc7b2..8d4da860 100644 --- a/src/features/agents/agent-form.tsx +++ b/src/features/agents/components/agent-form.tsx @@ -15,9 +15,9 @@ import {Button} from "@/components/ui/button"; import {useRouter} from "next/navigation"; import {useMutation, useQueryClient} from "@tanstack/react-query"; import {TooltipProvider} from "@/components/ui/tooltip"; -import {AgentSchema, AgentType} from "@/features/agents/agents.schema"; +import {AgentSchema, AgentType} from "@/features/agents/schemas/agents.schema"; import {toast} from "sonner"; -import {createAgentAction, updateAgentAction} from "@/features/agents/agents.action"; +import {createAgentAction, updateAgentAction} from "@/features/agents/actions/agents.action"; import {OrganizationWithMembers} from "@/db/schema/03_organization"; export type agentFormProps = { diff --git a/src/features/agents/agent-key-modal.tsx b/src/features/agents/components/agent-key-modal.tsx similarity index 100% rename from src/features/agents/agent-key-modal.tsx rename to src/features/agents/components/agent-key-modal.tsx diff --git a/src/features/agents/agent-organizations.form.tsx b/src/features/agents/components/agent-organizations.form.tsx similarity index 97% rename from src/features/agents/agent-organizations.form.tsx rename to src/features/agents/components/agent-organizations.form.tsx index 197eb98f..797b9469 100644 --- a/src/features/agents/agent-organizations.form.tsx +++ b/src/features/agents/components/agent-organizations.form.tsx @@ -8,8 +8,8 @@ import {OrganizationWithMembers} from "@/db/schema/03_organization"; import {MultiSelect} from "@/components/common/multi-select"; import {toast} from "sonner"; import {AgentWith} from "@/db/schema/08_agent"; -import {AgentOrganizationSchema, AgentOrganizationType} from "@/features/agents/agent-organizations.schema"; -import {updateAgentOrganizationsAction} from "@/features/agents/agent-organizations.action"; +import {AgentOrganizationSchema, AgentOrganizationType} from "@/features/agents/schemas/agent-organizations.schema"; +import {updateAgentOrganizationsAction} from "@/features/agents/actions/agent-organizations.action"; type AgentOrganisationFormProps = { diff --git a/src/features/agents/use-agent-update-check.ts b/src/features/agents/hooks/use-agent-update-check.ts similarity index 100% rename from src/features/agents/use-agent-update-check.ts rename to src/features/agents/hooks/use-agent-update-check.ts diff --git a/src/features/agents/agent-organizations.schema.ts b/src/features/agents/schemas/agent-organizations.schema.ts similarity index 100% rename from src/features/agents/agent-organizations.schema.ts rename to src/features/agents/schemas/agent-organizations.schema.ts diff --git a/src/features/agents/agents.schema.ts b/src/features/agents/schemas/agents.schema.ts similarity index 100% rename from src/features/agents/agents.schema.ts rename to src/features/agents/schemas/agents.schema.ts diff --git a/src/features/onboarding/actions/get-agent-status.action.ts b/src/features/onboarding/actions/get-agent-status.action.ts index 00cc5df9..3b10e81a 100644 --- a/src/features/onboarding/actions/get-agent-status.action.ts +++ b/src/features/onboarding/actions/get-agent-status.action.ts @@ -2,7 +2,7 @@ import { userAction } from "@/lib/safe-actions/actions"; import { z } from "zod"; -import { getAgentAction } from "@/features/agents/agents.action"; +import { getAgentAction } from "@/features/agents/actions/agents.action"; export const getAgentStatusAction = userAction .schema(z.object({ agentId: z.string() })) diff --git a/src/features/onboarding/hooks/use-create-agent.ts b/src/features/onboarding/hooks/use-create-agent.ts index a59a54ab..4bcfc305 100644 --- a/src/features/onboarding/hooks/use-create-agent.ts +++ b/src/features/onboarding/hooks/use-create-agent.ts @@ -3,7 +3,7 @@ import { useMutation } from "@tanstack/react-query"; import { useOnboarding } from "@onboardjs/react"; import { toast } from "sonner"; -import { createAgentAction } from "@/features/agents/agents.action"; +import { createAgentAction } from "@/features/agents/actions/agents.action"; import type { OnboardingAgent, OnboardingDefaultsData } from "@/features/onboarding/types"; import { generateEdgeKey } from "@/utils/edge_key"; import { getServerUrl } from "@/utils/get-server-url"; diff --git a/src/features/onboarding/hooks/use-delete-agent.ts b/src/features/onboarding/hooks/use-delete-agent.ts index 3206be25..cc363d36 100644 --- a/src/features/onboarding/hooks/use-delete-agent.ts +++ b/src/features/onboarding/hooks/use-delete-agent.ts @@ -3,7 +3,7 @@ import { useMutation } from "@tanstack/react-query"; import { useOnboarding } from "@onboardjs/react"; import { toast } from "sonner"; -import { deleteAgentAction } from "@/features/agents/agent-delete.action"; +import { deleteAgentAction } from "@/features/agents/actions/agent-delete.action"; import type { OnboardingAgent } from "@/features/onboarding/types"; export const useDeleteAgent = () => { diff --git a/src/features/onboarding/steps/step-agent-key.tsx b/src/features/onboarding/steps/step-agent-key.tsx index ba1b9490..ca9f2632 100644 --- a/src/features/onboarding/steps/step-agent-key.tsx +++ b/src/features/onboarding/steps/step-agent-key.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import { useOnboarding } from "@onboardjs/react"; import { Server } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { AgentCardKey } from "@/features/agents/agent-card-key"; +import { AgentCardKey } from "@/features/agents/components/agent-card-key"; import type { OnboardingAgent } from "@/features/onboarding/types"; import { cn } from "@/lib/utils"; diff --git a/src/features/organizations/organization-agents-tab.tsx b/src/features/organizations/organization-agents-tab.tsx index abb57e0f..a1aa508d 100644 --- a/src/features/organizations/organization-agents-tab.tsx +++ b/src/features/organizations/organization-agents-tab.tsx @@ -2,8 +2,8 @@ import {OrganizationWithMembers} from "@/db/schema/03_organization"; import {cn} from "@/lib/utils"; import {Agent} from "@/db/schema/08_agent"; import {CardsWithPagination} from "@/components/common/cards-with-pagination"; -import {AgentCard} from "@/features/agents/agent-card"; -import {AgentDialog} from "@/features/agents/agent-dialog"; +import {AgentCard} from "@/features/agents/components/agent-card"; +import {AgentDialog} from "@/features/agents/components/agent-dialog"; export type OrganizationAgentsTabProps = { organization: OrganizationWithMembers; diff --git a/src/features/settings/storage-section.tsx b/src/features/settings/storage-section.tsx index b8c39718..a0e97009 100644 --- a/src/features/settings/storage-section.tsx +++ b/src/features/settings/storage-section.tsx @@ -26,7 +26,7 @@ import { } from "@/features/settings/storage.action"; import {toast} from "sonner"; import {Switch} from "@/components/ui/switch"; -import {downloadMasterKeyAction} from "@/features/agents/keys.action"; +import {downloadMasterKeyAction} from "@/features/agents/actions/keys.action"; export type SettingsStorageSectionProps = { settings: Setting; diff --git a/src/lib/api-v1/openapi/routes/agents.ts b/src/lib/api-v1/openapi/routes/agents.ts index 1f5b26f6..578304cc 100644 --- a/src/lib/api-v1/openapi/routes/agents.ts +++ b/src/lib/api-v1/openapi/routes/agents.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { OpenAPIRegistry } from "@asteasolutions/zod-to-openapi"; import "@/lib/api-v1/openapi/registry"; -import {AgentSchema} from "@/features/agents/agents.schema"; +import {AgentSchema} from "@/features/agents/schemas/agents.schema"; import {agentSchema} from "@/db/schema/08_agent"; const UuidParam = z diff --git a/src/lib/auth/current-user.ts b/src/lib/auth/current-user.ts index 9c0440b8..a3d2419b 100644 --- a/src/lib/auth/current-user.ts +++ b/src/lib/auth/current-user.ts @@ -1,16 +1,16 @@ "use server"; import { auth } from "@/lib/auth/auth"; import { headers } from "next/headers"; -import {User} from "@/db/schema/02_user"; +import { User } from "@/db/schema/02_user"; export const currentUser = async () => { - const session = await auth.api.getSession({ - headers: await headers(), - }); + const session = await auth.api.getSession({ + headers: await headers(), + }); - if (!session?.user) { - return null; - } + if (!session?.user) { + return null; + } - return session.user as User; + return session.user as User; }; diff --git a/src/lib/auth/passkey-context.ts b/src/lib/auth/passkey-context.ts index d8a6c388..60e22534 100644 --- a/src/lib/auth/passkey-context.ts +++ b/src/lib/auth/passkey-context.ts @@ -2,19 +2,27 @@ import { createHmac } from "crypto"; import { env } from "@/env.mjs"; export function signPasskeyContext(name: string, email: string): string { - const payload = Buffer.from(JSON.stringify({ name, email, exp: Date.now() + 5 * 60 * 1000 })).toString("base64url"); - const sig = createHmac("sha256", env.PROJECT_SECRET).update(payload).digest("base64url"); - return `${payload}.${sig}`; + const payload = Buffer.from( + JSON.stringify({ name, email, exp: Date.now() + 5 * 60 * 1000 }), + ).toString("base64url"); + const sig = createHmac("sha256", env.PROJECT_SECRET) + .update(payload) + .digest("base64url"); + return `${payload}.${sig}`; } -export function verifyPasskeyContext(token: string): { name: string; email: string } | null { - const dot = token.lastIndexOf("."); - if (dot === -1) return null; - const payload = token.slice(0, dot); - const sig = token.slice(dot + 1); - const expected = createHmac("sha256", env.PROJECT_SECRET).update(payload).digest("base64url"); - if (sig !== expected) return null; - const data = JSON.parse(Buffer.from(payload, "base64url").toString()); - if (data.exp < Date.now()) return null; - return { name: data.name, email: data.email }; +export function verifyPasskeyContext( + token: string, +): { name: string; email: string } | null { + const dot = token.lastIndexOf("."); + if (dot === -1) return null; + const payload = token.slice(0, dot); + const sig = token.slice(dot + 1); + const expected = createHmac("sha256", env.PROJECT_SECRET) + .update(payload) + .digest("base64url"); + if (sig !== expected) return null; + const data = JSON.parse(Buffer.from(payload, "base64url").toString()); + if (data.exp < Date.now()) return null; + return { name: data.name, email: data.email }; } diff --git a/src/lib/auth/permissions.ts b/src/lib/auth/permissions.ts index c3227201..b8c56c8c 100644 --- a/src/lib/auth/permissions.ts +++ b/src/lib/auth/permissions.ts @@ -48,7 +48,6 @@ const pending = ac.newRole({ agent: [], }); -//org const orgMember = ac.newRole({ project: ["list"], database: ["list"], diff --git a/src/lib/email/helpers.ts b/src/lib/email/helpers.ts index 48e9a724..8477da9c 100644 --- a/src/lib/email/helpers.ts +++ b/src/lib/email/helpers.ts @@ -1,16 +1,16 @@ import nodemailer from "nodemailer"; -import {Server} from "./types" +import { Server } from "./types"; 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, - }, - }); -}; \ No newline at end of file + 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, + }, + }); +}; diff --git a/src/lib/email/index.ts b/src/lib/email/index.ts index c6ff38b2..f6b3f47a 100644 --- a/src/lib/email/index.ts +++ b/src/lib/email/index.ts @@ -1,42 +1,47 @@ -"use server" -import {db} from "@/db"; -import {eq} from "drizzle-orm"; +"use server"; +import { db } from "@/db"; +import { eq } from "drizzle-orm"; import * as drizzleDb from "@/db"; -import {createTransporter} from "@/lib/email/helpers"; -import {Payload} from "@/lib/email/types"; +import { createTransporter } from "@/lib/email/helpers"; +import { Payload } from "@/lib/email/types"; 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]); + 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) { + 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; - } + 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 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(); + 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, - }); + return await transporter.sendMail({ + ...data, + to: emailsArray, + from: settings.smtpFrom ?? undefined, + }); }; diff --git a/src/lib/email/types.ts b/src/lib/email/types.ts index 31b94be6..c3a1b809 100644 --- a/src/lib/email/types.ts +++ b/src/lib/email/types.ts @@ -1,19 +1,17 @@ "use server"; - export type Payload = { - to: string; - from?: string; - subject: string; - html: any; + to: string; + from?: string; + subject: string; + html: any; }; export type Server = { - host: string; - port: number; - user: string; - pass: string; - from: string; - secure: boolean; + host: string; + port: number; + user: string; + pass: string; + from: string; + secure: boolean; }; - diff --git a/src/lib/mcp/http-client.ts b/src/lib/mcp/http-client.ts index 0795ee24..db9aa93d 100644 --- a/src/lib/mcp/http-client.ts +++ b/src/lib/mcp/http-client.ts @@ -4,14 +4,10 @@ export type ApiV1Result = | { ok: true; status: number; data: unknown } | { ok: false; status: number; error: string }; -/** - * Thin fetch wrapper that proxies calls to the existing /api/v1 REST layer. - * Forwards the caller's API key so the REST route runs its own auth + permission checks. - */ export async function apiV1Fetch( path: string, options: RequestInit, - apiKey: string + apiKey: string, ): Promise { const url = `${getServerUrl()}${path}`; @@ -29,7 +25,6 @@ export async function apiV1Fetch( return { ok: false, status: 0, error: "Failed to reach internal API" }; } - // 204 No Content has no body if (res.status === 204) { return { ok: true, status: 204, data: null }; } @@ -53,9 +48,7 @@ export async function apiV1Fetch( } const data = - typeof body === "object" && - body !== null && - "data" in body + typeof body === "object" && body !== null && "data" in body ? (body as { data: unknown }).data : body; diff --git a/src/lib/mcp/server.ts b/src/lib/mcp/server.ts index c16ba81a..87adf727 100644 --- a/src/lib/mcp/server.ts +++ b/src/lib/mcp/server.ts @@ -14,7 +14,7 @@ import { registerBackupTools } from "./tools/backups"; */ export function createPortabaseMcpServer( _ctx: ApiKeyContext, - apiKey: string + apiKey: string, ): McpServer { const server = new McpServer({ name: "portabase", diff --git a/src/lib/mcp/tools/agents.ts b/src/lib/mcp/tools/agents.ts index a3b5a164..79f7eef5 100644 --- a/src/lib/mcp/tools/agents.ts +++ b/src/lib/mcp/tools/agents.ts @@ -1,8 +1,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; -import {apiV1Fetch} from "@/lib/mcp/http-client"; -import {err, ok} from "@/lib/mcp/tools/response"; - +import { apiV1Fetch } from "@/lib/mcp/http-client"; +import { err, ok } from "@/lib/mcp/tools/response"; export function registerAgentTools(server: McpServer, apiKey: string) { server.tool( @@ -10,9 +9,13 @@ export function registerAgentTools(server: McpServer, apiKey: string) { "List all agents accessible to the authenticated user", {}, async () => { - const result = await apiV1Fetch("/api/v1/agents", { method: "GET" }, apiKey); + const result = await apiV1Fetch( + "/api/v1/agents", + { method: "GET" }, + apiKey, + ); return result.ok ? ok(result.data) : err(result.error); - } + }, ); server.tool( @@ -20,9 +23,13 @@ export function registerAgentTools(server: McpServer, apiKey: string) { "Get details for a specific agent, including its associated databases", { id: z.string().describe("Agent ID") }, async ({ id }) => { - const result = await apiV1Fetch(`/api/v1/agents/${id}`, { method: "GET" }, apiKey); + const result = await apiV1Fetch( + `/api/v1/agents/${id}`, + { method: "GET" }, + apiKey, + ); return result.ok ? ok(result.data) : err(result.error); - } + }, ); server.tool( @@ -31,7 +38,6 @@ export function registerAgentTools(server: McpServer, apiKey: string) { { name: z.string().min(1).describe("Agent name"), organizationId: z - .string() .uuid() .optional() .describe("Organization ID to scope the agent to (optional)"), @@ -40,10 +46,10 @@ export function registerAgentTools(server: McpServer, apiKey: string) { const result = await apiV1Fetch( "/api/v1/agents", { method: "POST", body: JSON.stringify({ name, organizationId }) }, - apiKey + apiKey, ); return result.ok ? ok(result.data) : err(result.error); - } + }, ); server.tool( @@ -51,11 +57,15 @@ export function registerAgentTools(server: McpServer, apiKey: string) { "Delete an agent by ID", { id: z.string().describe("Agent ID") }, async ({ id }) => { - const result = await apiV1Fetch(`/api/v1/agents/${id}`, { method: "DELETE" }, apiKey); + const result = await apiV1Fetch( + `/api/v1/agents/${id}`, + { method: "DELETE" }, + apiKey, + ); return result.ok ? ok({ message: `Agent ${id} deleted successfully` }) : err(result.error); - } + }, ); server.tool( @@ -63,8 +73,12 @@ export function registerAgentTools(server: McpServer, apiKey: string) { "Get the edge key for an agent (used by the agent to authenticate with Portabase)", { id: z.string().describe("Agent ID") }, async ({ id }) => { - const result = await apiV1Fetch(`/api/v1/agents/${id}/key`, { method: "GET" }, apiKey); + const result = await apiV1Fetch( + `/api/v1/agents/${id}/key`, + { method: "GET" }, + apiKey, + ); return result.ok ? ok(result.data) : err(result.error); - } + }, ); } diff --git a/src/lib/mcp/tools/backups.ts b/src/lib/mcp/tools/backups.ts index 0aea0d18..ce0a2c9b 100644 --- a/src/lib/mcp/tools/backups.ts +++ b/src/lib/mcp/tools/backups.ts @@ -1,7 +1,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; -import {err, ok} from "@/lib/mcp/tools/response"; -import {apiV1Fetch} from "@/lib/mcp/http-client"; +import { err, ok } from "@/lib/mcp/tools/response"; +import { apiV1Fetch } from "@/lib/mcp/http-client"; export function registerBackupTools(server: McpServer, apiKey: string) { server.tool( @@ -12,10 +12,10 @@ export function registerBackupTools(server: McpServer, apiKey: string) { const result = await apiV1Fetch( `/api/v1/databases/${databaseId}/backup`, { method: "GET" }, - apiKey + apiKey, ); return result.ok ? ok(result.data) : err(result.error); - } + }, ); server.tool( @@ -29,10 +29,10 @@ export function registerBackupTools(server: McpServer, apiKey: string) { const result = await apiV1Fetch( `/api/v1/databases/${databaseId}/backup/${backupId}`, { method: "GET" }, - apiKey + apiKey, ); return result.ok ? ok(result.data) : err(result.error); - } + }, ); server.tool( @@ -43,10 +43,10 @@ export function registerBackupTools(server: McpServer, apiKey: string) { const result = await apiV1Fetch( `/api/v1/databases/${databaseId}/backup`, { method: "POST", body: JSON.stringify({}) }, - apiKey + apiKey, ); return result.ok ? ok(result.data) : err(result.error); - } + }, ); server.tool( @@ -56,7 +56,6 @@ export function registerBackupTools(server: McpServer, apiKey: string) { databaseId: z.string().describe("Database ID"), backupId: z.string().uuid().describe("Backup ID"), backupStorageId: z - .string() .uuid() .describe("Backup storage ID (from get_backup storages list)"), }, @@ -67,9 +66,9 @@ export function registerBackupTools(server: McpServer, apiKey: string) { method: "POST", body: JSON.stringify({ backupId, backupStorageId }), }, - apiKey + apiKey, ); return result.ok ? ok(result.data) : err(result.error); - } + }, ); } diff --git a/src/lib/mcp/tools/databases.ts b/src/lib/mcp/tools/databases.ts index 7c39d4ff..614d0fb5 100644 --- a/src/lib/mcp/tools/databases.ts +++ b/src/lib/mcp/tools/databases.ts @@ -1,41 +1,48 @@ -import {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js"; -import {z} from "zod"; -import {err, ok} from "@/lib/mcp/tools/response"; -import {apiV1Fetch} from "@/lib/mcp/http-client"; - +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { err, ok } from "@/lib/mcp/tools/response"; +import { apiV1Fetch } from "@/lib/mcp/http-client"; export function registerDatabaseTools(server: McpServer, apiKey: string) { - server.tool( - "list_databases", - "List all databases accessible to the authenticated user", - {}, - async () => { - const result = await apiV1Fetch("/api/v1/databases", {method: "GET"}, apiKey); - return result.ok ? ok(result.data) : err(result.error); - } - ); + server.tool( + "list_databases", + "List all databases accessible to the authenticated user", + {}, + async () => { + const result = await apiV1Fetch( + "/api/v1/databases", + { method: "GET" }, + apiKey, + ); + return result.ok ? ok(result.data) : err(result.error); + }, + ); - server.tool( - "get_database", - "Get details for a specific database", - {id: z.string().describe("Database ID")}, - async ({id}) => { - const result = await apiV1Fetch(`/api/v1/databases/${id}`, {method: "GET"}, apiKey); - return result.ok ? ok(result.data) : err(result.error); - } - ); + server.tool( + "get_database", + "Get details for a specific database", + { id: z.string().describe("Database ID") }, + async ({ id }) => { + const result = await apiV1Fetch( + `/api/v1/databases/${id}`, + { method: "GET" }, + apiKey, + ); + return result.ok ? ok(result.data) : err(result.error); + }, + ); - server.tool( - "get_database_status", - "Get the current status of a database, including latest backup and restoration state", - {id: z.string().describe("Database ID")}, - async ({id}) => { - const result = await apiV1Fetch( - `/api/v1/databases/${id}/status`, - {method: "GET"}, - apiKey - ); - return result.ok ? ok(result.data) : err(result.error); - } - ); + server.tool( + "get_database_status", + "Get the current status of a database, including latest backup and restoration state", + { id: z.string().describe("Database ID") }, + async ({ id }) => { + const result = await apiV1Fetch( + `/api/v1/databases/${id}/status`, + { method: "GET" }, + apiKey, + ); + return result.ok ? ok(result.data) : err(result.error); + }, + ); } diff --git a/src/lib/mcp/tools/response.ts b/src/lib/mcp/tools/response.ts index 7e172aac..840dc214 100644 --- a/src/lib/mcp/tools/response.ts +++ b/src/lib/mcp/tools/response.ts @@ -1,12 +1,12 @@ export function ok(data: unknown) { - return { - content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }], - }; + return { + content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }], + }; } export function err(message: string) { - return { - content: [{ type: "text" as const, text: message }], - isError: true as const, - }; -} \ No newline at end of file + return { + content: [{ type: "text" as const, text: message }], + isError: true as const, + }; +} diff --git a/src/lib/safe-actions/actions.ts b/src/lib/safe-actions/actions.ts index aba266c6..148179a9 100644 --- a/src/lib/safe-actions/actions.ts +++ b/src/lib/safe-actions/actions.ts @@ -1,29 +1,29 @@ -import {createSafeActionClient} from "next-safe-action"; -import {currentUser} from "@/lib/auth/current-user"; +import { createSafeActionClient } from "next-safe-action"; +import { currentUser } from "@/lib/auth/current-user"; export class ActionError extends Error { - constructor(message: string) { - super(message); - this.name = "ActionError"; - } + constructor(message: string) { + super(message); + this.name = "ActionError"; + } } const handleReturnedServerError = (error: Error) => { - if (error instanceof ActionError) { - return error.message; - } else { - return "An unexpected error occurred."; - } + if (error instanceof ActionError) { + return error.message; + } else { + return "An unexpected error occurred."; + } }; export const action = createSafeActionClient({ - handleServerError: handleReturnedServerError, + handleServerError: handleReturnedServerError, }); -export const userAction = action.use(async ({next, ctx}) => { - const user = await currentUser(); - if (!user) { - throw new ActionError("You must be logged in"); - } - return next({ctx: {user}}); +export const userAction = action.use(async ({ next }) => { + const user = await currentUser(); + if (!user) { + throw new ActionError("You must be logged in"); + } + return next({ ctx: { user } }); }); diff --git a/src/lib/tasks/database/retention-days.ts b/src/lib/tasks/database/retention-days.ts index 198fa223..942bc2ac 100644 --- a/src/lib/tasks/database/retention-days.ts +++ b/src/lib/tasks/database/retention-days.ts @@ -1,45 +1,51 @@ -import {db} from "@/db"; -import {eq, lt, and, desc, isNull} from "drizzle-orm"; +import { db } from "@/db"; +import { eq, lt, and, isNull } from "drizzle-orm"; import * as drizzleDb from "@/db"; -import {deleteBackupCronAction} from "@/lib/tasks/database/utils/delete"; -import {logger} from "@/lib/logger"; +import { deleteBackupCronAction } from "@/lib/tasks/database/utils/delete"; +import { logger } from "@/lib/logger"; -const log = logger.child({module: "tasks/database/retention-days"}); +const log = logger.child({ module: "tasks/database/retention-days" }); export async function enforceRetentionDays(databaseId: string, days: number) { - log.info({ name: "enforceRetentionDays"}, `Enforce Retention Days starting for ${databaseId}`); + log.info( + { name: "enforceRetentionDays" }, + `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({ - where: and( - eq(drizzleDb.schemas.backup.databaseId, databaseId), - lt(drizzleDb.schemas.backup.createdAt, cutoff), - isNull(drizzleDb.schemas.backup.deletedAt) - ), + const expiredBackups = await db.query.backup.findMany({ + where: and( + eq(drizzleDb.schemas.backup.databaseId, databaseId), + lt(drizzleDb.schemas.backup.createdAt, cutoff), + isNull(drizzleDb.schemas.backup.deletedAt), + ), + with: { + database: { with: { - database: { - with: { - project: true - } - } - } + project: true, + }, + }, + }, + }); + + for (const backup of expiredBackups) { + const result = await deleteBackupCronAction({ + backupId: backup.id, + databaseId: backup.databaseId, }); - for (const backup of expiredBackups) { - - const result = await deleteBackupCronAction({ - backupId: backup.id, - databaseId: backup.databaseId, - }); - - const inner = result?.data; - if (inner?.success) { - log.info({ name: "enforceRetentionDays"}, `(databaseId:${backup.databaseId}) - (backupId: ${backup.id}) : successfully deleted`); - } else { - log.info({ name: "enforceRetentionDays"}, `(databaseId:${backup.databaseId}) - (backupId: ${backup.id}) : an error occurred - ${inner?.actionError?.message}`); - } + const inner = result?.data; + if (inner?.success) { + log.info( + { name: "enforceRetentionDays" }, + `(databaseId:${backup.databaseId}) - (backupId: ${backup.id}) : successfully deleted`, + ); + } else { + log.info( + { name: "enforceRetentionDays" }, + `(databaseId:${backup.databaseId}) - (backupId: ${backup.id}) : an error occurred - ${inner?.actionError?.message}`, + ); } - + } } - diff --git a/src/lib/tasks/database/retention-gsf.ts b/src/lib/tasks/database/retention-gsf.ts index e3b0271c..0db4a0ff 100644 --- a/src/lib/tasks/database/retention-gsf.ts +++ b/src/lib/tasks/database/retention-gsf.ts @@ -1,81 +1,106 @@ -import {db} from "@/db"; -import {subDays, subWeeks, subMonths, subYears, startOfWeek, startOfMonth, startOfYear} from "date-fns"; -import {eq, desc, isNull, and} from "drizzle-orm"; +import { db } from "@/db"; +import { + subDays, + subWeeks, + subMonths, + subYears, + startOfWeek, + startOfMonth, + startOfYear, +} from "date-fns"; +import { eq, desc, isNull, and } from "drizzle-orm"; import * as drizzleDb from "@/db"; -import {deleteBackupCronAction} from "@/lib/tasks/database/utils/delete"; -import {logger} from "@/lib/logger"; +import { deleteBackupCronAction } from "@/lib/tasks/database/utils/delete"; +import { logger } from "@/lib/logger"; -const log = logger.child({module: "tasks/database/retention-gsf"}); +const log = logger.child({ module: "tasks/database/retention-gsf" }); -export async function enforceRetentionGFS(databaseId: string, gfsSettings: { +export async function enforceRetentionGFS( + databaseId: string, + gfsSettings: { daily: number; weekly: number; monthly: number; yearly: number; -}) { - log.info({ name: "enforceRetentionGFS"}, `Retention GFS started for databaseId: ${databaseId}`); + }, +) { + log.info( + { name: "enforceRetentionGFS" }, + `Retention GFS started for databaseId: ${databaseId}`, + ); - const backups = await db.query.backup.findMany({ - where: and(eq(drizzleDb.schemas.backup.databaseId, databaseId), isNull(drizzleDb.schemas.backup.deletedAt)), - orderBy: desc(drizzleDb.schemas.backup.createdAt), + const backups = await db.query.backup.findMany({ + where: and( + eq(drizzleDb.schemas.backup.databaseId, databaseId), + isNull(drizzleDb.schemas.backup.deletedAt), + ), + orderBy: desc(drizzleDb.schemas.backup.createdAt), + with: { + database: { with: { - database: { - with: { - project: true - } - } - } - }); + project: true, + }, + }, + }, + }); - const now = new Date(); - const toKeep: Set = new Set(); + const now = new Date(); + const toKeep: Set = new Set(); - // DAILY - backups.forEach((b) => { - if (b.createdAt >= subDays(now, gfsSettings.daily)) toKeep.add(b.id); - }); + backups.forEach((b) => { + if (b.createdAt >= subDays(now, gfsSettings.daily)) toKeep.add(b.id); + }); - const weekStartDates = Array.from({length: gfsSettings.weekly}, (_, i) => startOfWeek(subWeeks(now, i), {weekStartsOn: 1})); - weekStartDates.forEach((weekStart) => { - const backupOfWeek = backups.find( - (b) => b.createdAt >= weekStart && b.createdAt < subWeeks(weekStart, -1) + const weekStartDates = Array.from({ length: gfsSettings.weekly }, (_, i) => + startOfWeek(subWeeks(now, i), { weekStartsOn: 1 }), + ); + weekStartDates.forEach((weekStart) => { + const backupOfWeek = backups.find( + (b) => b.createdAt >= weekStart && b.createdAt < subWeeks(weekStart, -1), + ); + if (backupOfWeek) toKeep.add(backupOfWeek.id); + }); + + const monthStartDates = Array.from({ length: gfsSettings.monthly }, (_, i) => + startOfMonth(subMonths(now, i)), + ); + monthStartDates.forEach((monthStart) => { + const backupOfMonth = backups.find( + (b) => + b.createdAt >= monthStart && b.createdAt < subMonths(monthStart, -1), + ); + if (backupOfMonth) toKeep.add(backupOfMonth.id); + }); + + const yearStartDates = Array.from({ length: gfsSettings.yearly }, (_, i) => + startOfYear(subYears(now, i)), + ); + yearStartDates.forEach((yearStart) => { + const backupOfYear = backups.find( + (b) => b.createdAt >= yearStart && b.createdAt < subYears(yearStart, -1), + ); + if (backupOfYear) toKeep.add(backupOfYear.id); + }); + + for (const b of backups) { + if (!toKeep.has(b.id)) { + const result = await deleteBackupCronAction({ + backupId: b.id, + databaseId: b.databaseId, + }); + + const inner = result?.data; + if (inner?.success) { + log.info( + { name: "enforceRetentionGFS" }, + `(databaseId:${b.databaseId}) - (backupId: ${b.id}) : successfully deleted`, ); - if (backupOfWeek) toKeep.add(backupOfWeek.id); - }); - - // MONTHLY - const monthStartDates = Array.from({length: gfsSettings.monthly}, (_, i) => startOfMonth(subMonths(now, i))); - monthStartDates.forEach((monthStart) => { - const backupOfMonth = backups.find( - (b) => b.createdAt >= monthStart && b.createdAt < subMonths(monthStart, -1) + } else { + log.info( + { name: "enforceRetentionGFS" }, + `(databaseId:${b.databaseId}) - (backupId: ${b.id}) : an error occurred - ${inner?.actionError?.message}`, ); - if (backupOfMonth) toKeep.add(backupOfMonth.id); - }); - - // YEARLY - const yearStartDates = Array.from({length: gfsSettings.yearly}, (_, i) => startOfYear(subYears(now, i))); - yearStartDates.forEach((yearStart) => { - const backupOfYear = backups.find( - (b) => b.createdAt >= yearStart && b.createdAt < subYears(yearStart, -1) - ); - if (backupOfYear) toKeep.add(backupOfYear.id); - }); - - // Delete backups not in `toKeep` - for (const b of backups) { - if (!toKeep.has(b.id)) { - - const result = await deleteBackupCronAction({ - backupId: b.id, - databaseId: b.databaseId, - }); - - const inner = result?.data; - if (inner?.success) { - log.info({ name: "enforceRetentionGFS"}, `(databaseId:${b.databaseId}) - (backupId: ${b.id}) : successfully deleted`); - } else { - log.info({ name: "enforceRetentionGFS"}, `(databaseId:${b.databaseId}) - (backupId: ${b.id}) : an error occurred - ${inner?.actionError?.message}`); - } - } + } } + } } diff --git a/src/utils/edge_key.ts b/src/utils/edge_key.ts index 0e1d0ef0..f27ae519 100644 --- a/src/utils/edge_key.ts +++ b/src/utils/edge_key.ts @@ -1,5 +1,5 @@ "use server" -import {getMasterServerKeyContent} from "@/features/agents/keys.action"; +import {getMasterServerKeyContent} from "@/features/agents/actions/keys.action"; export async function generateEdgeKey(serverUrl: string, agentId: string): Promise { const masterKey = await getMasterServerKeyContent()