From fb2fab01d702507d5b4984bf70a26cae18a5215f Mon Sep 17 00:00:00 2001 From: Rajat Date: Mon, 10 Nov 2025 04:27:57 +0000 Subject: [PATCH 1/8] Communities data is cleaned up on delete --- apps/queue/src/index.ts | 2 +- apps/web/__mocks__/medialit.ts | 28 +++ .../dashboard/(sidebar)/blog/[id]/page.tsx | 9 +- .../dashboard/(sidebar)/product/[id]/page.tsx | 3 +- .../components/admin/blogs/editor/details.tsx | 25 +- apps/web/components/admin/blogs/helpers.ts | 25 +- apps/web/components/admin/mails/index.tsx | 10 +- apps/web/components/community/banner.tsx | 5 +- apps/web/components/community/index.tsx | 15 +- apps/web/components/ui/sonner.tsx | 44 ++++ apps/web/graphql/communities/logic.ts | 166 ++++++++++++- apps/web/graphql/courses/logic.ts | 39 ++- apps/web/graphql/lessons/logic.ts | 49 ++-- .../web/graphql/pages/__tests__/logic.test.ts | 225 ++++++++++++++++++ apps/web/graphql/pages/logic.ts | 46 +++- apps/web/lib/get-deleted-media-ids.ts | 42 ++++ apps/web/next-env.d.ts | 1 - apps/web/package.json | 1 + apps/web/services/medialit.ts | 127 ++-------- apps/web/ui-config/strings.ts | 1 + pnpm-lock.yaml | 14 ++ 21 files changed, 690 insertions(+), 187 deletions(-) create mode 100644 apps/web/__mocks__/medialit.ts create mode 100644 apps/web/components/ui/sonner.tsx create mode 100644 apps/web/graphql/pages/__tests__/logic.test.ts create mode 100644 apps/web/lib/get-deleted-media-ids.ts diff --git a/apps/queue/src/index.ts b/apps/queue/src/index.ts index 437e98a3d..806350bca 100644 --- a/apps/queue/src/index.ts +++ b/apps/queue/src/index.ts @@ -17,7 +17,7 @@ app.use("/job", verifyJWTMiddleware, jobRoutes); app.use("/sse", sseRoutes); app.get("/healthy", (req, res) => { - res.status(200).json({ success: true }); + res.status(200).json({ status: "ok", uptime: process.uptime() }); }); startEmailAutomation(); diff --git a/apps/web/__mocks__/medialit.ts b/apps/web/__mocks__/medialit.ts new file mode 100644 index 000000000..5bc291a7c --- /dev/null +++ b/apps/web/__mocks__/medialit.ts @@ -0,0 +1,28 @@ +class MediaLit { + endpoint: string; + constructor(config: { endpoint?: string }) { + this.endpoint = config.endpoint || "https://medialit.example.com"; + } + + async get(mediaId: string) { + return { + mediaId, + file: "mock-file", + originalFileName: "mock-file", + mimeType: "image/png", + size: 0, + access: "public", + url: `https://medialit.example.com/${mediaId}/main.png`, + }; + } + + async getSignature(_: { group: string }) { + return "mock-signature"; + } + + async delete(_: string) { + return true; + } +} + +export { MediaLit }; diff --git a/apps/web/app/(with-contexts)/dashboard/(sidebar)/blog/[id]/page.tsx b/apps/web/app/(with-contexts)/dashboard/(sidebar)/blog/[id]/page.tsx index b67903da7..d2a7da168 100644 --- a/apps/web/app/(with-contexts)/dashboard/(sidebar)/blog/[id]/page.tsx +++ b/apps/web/app/(with-contexts)/dashboard/(sidebar)/blog/[id]/page.tsx @@ -17,6 +17,7 @@ import { } from "@courselit/components-library"; import { MoreVert } from "@courselit/icons"; import { + APP_MESSAGE_COURSE_DELETED, DELETE_PRODUCT_POPUP_HEADER, DELETE_PRODUCT_POPUP_TEXT, EDIT_BLOG, @@ -24,6 +25,7 @@ import { MENU_BLOG_VISIT, PAGE_TITLE_404, PRODUCT_TABLE_CONTEXT_MENU_DELETE_PRODUCT, + TOAST_TITLE_SUCCESS, } from "@ui-config/strings"; import { truncate } from "@ui-lib/utils"; import { useRouter, useSearchParams } from "next/navigation"; @@ -40,7 +42,7 @@ export default function Page(props: { params: Promise<{ id: string }> }) { const searchParams = useSearchParams(); const [tab, setTab] = useState(searchParams?.get("tab") || "Details"); const address = useContext(AddressContext); - const course = useCourse(id, address); + const course = useCourse(id); const router = useRouter(); const { toast } = useToast(); @@ -90,6 +92,11 @@ export default function Page(props: { params: Promise<{ id: string }> }) { router.replace( `/dashboard/blogs`, ); + toast({ + title: TOAST_TITLE_SUCCESS, + description: + APP_MESSAGE_COURSE_DELETED, + }); }, toast, }) diff --git a/apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/page.tsx b/apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/page.tsx index cdeebf826..7651f3107 100644 --- a/apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/page.tsx +++ b/apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/page.tsx @@ -41,6 +41,7 @@ import { PRODUCT_EMPTY_WARNING, PRODUCT_TABLE_CONTEXT_MENU_INVITE_A_CUSTOMER, PRODUCT_UNPUBLISHED_WARNING, + MANAGE_LINK_TEXT, TOAST_TITLE_SUCCESS, VIEW_PAGE_MENU_ITEM, } from "@ui-config/strings"; @@ -112,7 +113,7 @@ export default function DashboardPage() { href={`/dashboard/product/${productId}/manage#publish`} className="underline" > - Manage + {MANAGE_LINK_TEXT} )} diff --git a/apps/web/components/admin/blogs/editor/details.tsx b/apps/web/components/admin/blogs/editor/details.tsx index 9c4b4f9d9..77792d87d 100644 --- a/apps/web/components/admin/blogs/editor/details.tsx +++ b/apps/web/components/admin/blogs/editor/details.tsx @@ -56,11 +56,11 @@ export default function Details({ id }: DetailsProps) { e.preventDefault(); const mutation = ` - mutation { + mutation ($courseId: String!, $title: String!, $description: String) { updateCourse(courseData: { - id: "${course!.courseId}" - title: "${title}", - description: ${JSON.stringify(JSON.stringify(description))} + id: $courseId + title: $title + description: $description }) { courseId } @@ -68,18 +68,19 @@ export default function Details({ id }: DetailsProps) { `; const fetch = new FetchBuilder() .setUrl(`${address.backend}/api/graph`) - .setPayload(mutation) + .setPayload({ + query: mutation, + variables: { + courseId: course!.courseId, + title: title, + description: JSON.stringify(description), + }, + }) .setIsGraphQLEndpoint(true) .build(); try { setLoading(true); - const response = await fetch.exec(); - if (response.updateCourse) { - toast({ - title: TOAST_TITLE_SUCCESS, - description: APP_MESSAGE_COURSE_SAVED, - }); - } + await fetch.exec(); } catch (err: any) { toast({ title: TOAST_TITLE_ERROR, diff --git a/apps/web/components/admin/blogs/helpers.ts b/apps/web/components/admin/blogs/helpers.ts index e03a97382..41a8b4c45 100644 --- a/apps/web/components/admin/blogs/helpers.ts +++ b/apps/web/components/admin/blogs/helpers.ts @@ -1,9 +1,5 @@ import { FetchBuilder } from "@courselit/utils"; -import { - APP_MESSAGE_COURSE_DELETED, - TOAST_TITLE_ERROR, - TOAST_TITLE_SUCCESS, -} from "@/ui-config/strings"; +import { TOAST_TITLE_ERROR } from "@/ui-config/strings"; import { useToast } from "@courselit/components-library"; interface DeleteProductProps { @@ -22,14 +18,19 @@ export const deleteProduct = async ({ if (!id) return; const query = ` - mutation { - result: deleteCourse(id: "${id}") - } + mutation ($id: String!) { + result: deleteCourse(id: $id) + } `; const fetch = new FetchBuilder() .setUrl(`${backend}/api/graph`) - .setPayload(query) + .setPayload({ + query, + variables: { + id, + }, + }) .setIsGraphQLEndpoint(true) .build(); @@ -38,7 +39,6 @@ export const deleteProduct = async ({ if (response.result) { onDeleteComplete && onDeleteComplete(); - // onDelete(position); } } catch (err: any) { toast({ @@ -46,10 +46,5 @@ export const deleteProduct = async ({ description: err.message, variant: "destructive", }); - } finally { - toast({ - title: TOAST_TITLE_SUCCESS, - description: APP_MESSAGE_COURSE_DELETED, - }); } }; diff --git a/apps/web/components/admin/mails/index.tsx b/apps/web/components/admin/mails/index.tsx index 089d39812..d2682159a 100644 --- a/apps/web/components/admin/mails/index.tsx +++ b/apps/web/components/admin/mails/index.tsx @@ -29,6 +29,7 @@ import { import RequestForm from "./request-form"; import SequencesList from "./sequences-list"; import { Button } from "@components/ui/button"; +import Link from "next/link"; interface MailsProps { address: Address; @@ -157,12 +158,9 @@ export default function Mails({ address, selectedTab }: MailsProps) {
- + + +
diff --git a/apps/web/components/community/banner.tsx b/apps/web/components/community/banner.tsx index 013127e2d..208e685b3 100644 --- a/apps/web/components/community/banner.tsx +++ b/apps/web/components/community/banner.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, useContext } from "react"; import { Button } from "@/components/ui/button"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { AlertCircle, Pencil, Check, X, Loader2 } from "lucide-react"; @@ -10,6 +10,7 @@ import { } from "@courselit/components-library"; import { isTextEditorNonEmpty } from "@ui-lib/utils"; import { BUTTON_SAVING, TOAST_TITLE_SUCCESS } from "@ui-config/strings"; +import { AddressContext } from "@components/contexts"; interface BannerComponentProps { canEdit: boolean; @@ -29,6 +30,7 @@ export default function Banner({ const [editedBannerText, setEditedBannerText] = useState(bannerText); const [isSaving, setIsSaving] = useState(false); const textareaRef = useRef(null); + const address = useContext(AddressContext); const { toast } = useToast(); useEffect(() => { @@ -102,6 +104,7 @@ export default function Banner({ showToolbar={false} initialContent={editedBannerText} onChange={(value) => setEditedBannerText(value)} + url={address.backend} />