Skip to content

Commit 424d558

Browse files
author
Rajat
committed
Re-worked social image management
1 parent 5bfd790 commit 424d558

File tree

7 files changed

+137
-22
lines changed

7 files changed

+137
-22
lines changed

apps/web/components/admin/page-editor/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export default function PageEditor({
9090
Page & {
9191
draftTitle?: string;
9292
draftDescription?: string;
93-
draftSocialImage?: Media;
93+
draftSocialImage?: Media | null;
9494
draftRobotsAllowed?: boolean;
9595
}
9696
>
@@ -595,7 +595,9 @@ export default function PageEditor({
595595
: true
596596
}
597597
socialImage={
598-
page.draftSocialImage ?? page.socialImage ?? null
598+
page.draftSocialImage !== undefined
599+
? page.draftSocialImage
600+
: (page.socialImage ?? null)
599601
}
600602
onClose={(e) => setLeftPaneContent("none")}
601603
onSave={({

apps/web/graphql/pages/__tests__/logic.test.ts

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import PageModel, { Page } from "@/models/Page";
88
import Course from "@/models/Course";
99
import CommunityModel from "@/models/Community";
1010
import constants from "@/config/constants";
11-
import { deleteMedia } from "@/services/medialit";
11+
import { deleteMedia, sealMedia } from "@/services/medialit";
1212
import GQLContext from "@/models/GQLContext";
1313
import { responses } from "@/config/strings";
1414

@@ -844,6 +844,92 @@ describe("Media cleanup", () => {
844844
expect(deleteMedia).not.toHaveBeenCalledWith(media1);
845845
});
846846

847+
it("sets draftSocialImage to null when socialImage is null", async () => {
848+
const page = await PageModel.create({
849+
domain: ctx.subdomain._id,
850+
pageId: "update-clears-draft-social-image",
851+
type: constants.site,
852+
creatorId: "creator-1",
853+
name: "Update Clears Draft Social Image",
854+
layout: [],
855+
draftLayout: [],
856+
draftSocialImage: mediaObj1,
857+
});
858+
859+
await updatePage({
860+
context: ctx,
861+
pageId: page.pageId,
862+
socialImage: null,
863+
});
864+
865+
const refreshedPage = await PageModel.findOne({
866+
_id: page._id,
867+
});
868+
869+
expect(refreshedPage?.draftSocialImage).toBeNull();
870+
expect(deleteMedia).toHaveBeenCalledWith(media1);
871+
});
872+
873+
it("does NOT delete the cleared draftSocialImage if published socialImage still uses it", async () => {
874+
const page = await PageModel.create({
875+
domain: ctx.subdomain._id,
876+
pageId: "update-clears-draft-but-protects-published-social",
877+
type: constants.site,
878+
creatorId: "creator-1",
879+
name: "Update Clears Draft But Protects Published Social",
880+
layout: [],
881+
draftLayout: [],
882+
socialImage: mediaObj1,
883+
draftSocialImage: mediaObj1,
884+
});
885+
886+
await updatePage({
887+
context: ctx,
888+
pageId: page.pageId,
889+
socialImage: null,
890+
});
891+
892+
const refreshedPage = await PageModel.findOne({
893+
_id: page._id,
894+
});
895+
896+
expect(refreshedPage?.draftSocialImage).toBeNull();
897+
expect(deleteMedia).not.toHaveBeenCalledWith(media1);
898+
});
899+
900+
it("does not delete the existing draftSocialImage when sealing the new social image fails", async () => {
901+
const page = await PageModel.create({
902+
domain: ctx.subdomain._id,
903+
pageId: "update-social-image-seal-failure",
904+
type: constants.site,
905+
creatorId: "creator-1",
906+
name: "Update Social Image Seal Failure",
907+
layout: [],
908+
draftLayout: [],
909+
draftSocialImage: mediaObj1,
910+
});
911+
912+
(sealMedia as jest.Mock).mockRejectedValueOnce(
913+
new Error("seal failed"),
914+
);
915+
916+
await expect(
917+
updatePage({
918+
context: ctx,
919+
pageId: page.pageId,
920+
socialImage: mediaObj2 as any,
921+
}),
922+
).rejects.toThrow("seal failed");
923+
924+
expect(deleteMedia).not.toHaveBeenCalledWith(media1);
925+
926+
const refreshedPage = await PageModel.findOne({
927+
_id: page._id,
928+
});
929+
930+
expect(refreshedPage?.draftSocialImage?.mediaId).toBe(media1);
931+
});
932+
847933
it("handles multiple media in a single widget", async () => {
848934
const page = await PageModel.create({
849935
domain: ctx.subdomain._id,
@@ -1091,6 +1177,29 @@ describe("Media cleanup", () => {
10911177
expect(deleteMedia).not.toHaveBeenCalledWith(media1);
10921178
});
10931179

1180+
it("clears published socialImage when draftSocialImage is null", async () => {
1181+
const page = await PageModel.create({
1182+
domain: ctx.subdomain._id,
1183+
pageId: "publish-clears-social-image",
1184+
type: constants.site,
1185+
creatorId: "creator-1",
1186+
name: "Publish Clears Social Image",
1187+
layout: [],
1188+
draftLayout: [],
1189+
socialImage: mediaObj1,
1190+
draftSocialImage: null,
1191+
});
1192+
1193+
await publish(page.pageId, ctx);
1194+
1195+
const refreshedPage = await PageModel.findOne({
1196+
_id: page._id,
1197+
});
1198+
1199+
expect(refreshedPage?.socialImage).toBeUndefined();
1200+
expect(deleteMedia).toHaveBeenCalledWith(media1);
1201+
});
1202+
10941203
it("handles publishing with empty draftLayout - media is deleted", async () => {
10951204
// Note: When draftLayout is empty, the layout is NOT copied (checked via `if (page.draftLayout.length)`)
10961205
// However, the media diff computation still happens: currentPublished - nextPublished

apps/web/graphql/pages/logic.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,9 @@ import { Domain } from "../../models/Domain";
1818
import { homePageTemplate } from "./page-templates";
1919
import { publishTheme } from "../themes/logic";
2020
import getDeletedMediaIds from "@/lib/get-deleted-media-ids";
21-
import { deleteMedia } from "@/services/medialit";
21+
import { deleteMedia, sealMedia } from "@/services/medialit";
2222
import CommunityModel from "@models/Community";
23-
import {
24-
replaceTempMediaWithSealedMediaInPageLayout,
25-
safeSealMedia,
26-
} from "@/lib/replace-temp-media-with-sealed-media-in-page-layout";
23+
import { replaceTempMediaWithSealedMediaInPageLayout } from "@/lib/replace-temp-media-with-sealed-media-in-page-layout";
2724
const { product, site, blogPage, communityPage, permissions, defaultPages } =
2825
constants;
2926
const { pageNames } = Constants;
@@ -137,7 +134,7 @@ export const updatePage = async ({
137134
layout?: string;
138135
title?: string;
139136
description?: string;
140-
socialImage?: Media;
137+
socialImage?: Media | null;
141138
robotsAllowed?: boolean;
142139
}): Promise<Partial<Page> | null> => {
143140
checkIfAuthenticated(ctx);
@@ -197,15 +194,18 @@ export const updatePage = async ({
197194
if (description) {
198195
page.draftDescription = description;
199196
}
200-
if (typeof socialImage !== "undefined" && socialImage?.mediaId) {
201-
if (page.draftSocialImage?.mediaId) {
202-
deletedMediaIds.push(page.draftSocialImage.mediaId);
197+
if (typeof socialImage !== "undefined") {
198+
const previousDraftSocialImageId = page.draftSocialImage?.mediaId;
199+
200+
if (socialImage === null) {
201+
page.draftSocialImage = null;
202+
} else if (socialImage.mediaId) {
203+
const sealedMedia = await sealMedia(socialImage.mediaId);
204+
page.draftSocialImage = sealedMedia;
203205
}
204-
if (socialImage?.mediaId) {
205-
const sealedMedia = await safeSealMedia(socialImage.mediaId);
206-
if (sealedMedia) {
207-
page.draftSocialImage = sealedMedia;
208-
}
206+
207+
if (previousDraftSocialImageId) {
208+
deletedMediaIds.push(previousDraftSocialImageId);
209209
}
210210
}
211211
if (typeof robotsAllowed === "boolean") {
@@ -292,7 +292,11 @@ export const publish = async (
292292
page.robotsAllowed = page.draftRobotsAllowed;
293293
// page.draftRobotsAllowed = undefined;
294294
}
295-
page.socialImage = page.draftSocialImage;
295+
if (page.draftSocialImage === null) {
296+
page.socialImage = undefined;
297+
} else if (typeof page.draftSocialImage !== "undefined") {
298+
page.socialImage = page.draftSocialImage;
299+
}
296300

297301
if (ctx.subdomain.themeId) {
298302
await publishTheme(ctx.subdomain.themeId, ctx);

apps/web/graphql/pages/mutation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const mutations = {
4040
layout?: string;
4141
title?: string;
4242
description?: string;
43-
socialImage?: Media;
43+
socialImage?: Media | null;
4444
robotsAllowed?: boolean;
4545
},
4646
context: GQLContext,

apps/web/models/Page.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export interface Page extends PublicPage {
1616
creatorId: string;
1717
draftTitle?: string;
1818
draftDescription?: string;
19-
draftSocialImage?: Media;
19+
draftSocialImage?: Media | null;
2020
draftRobotsAllowed?: boolean;
2121
}
2222

packages/common-models/src/page.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ export default interface Page {
1818
robotsAllowed?: boolean;
1919
draftTitle?: string;
2020
draftDescription?: string;
21-
draftSocialImage?: Media;
21+
draftSocialImage?: Media | null;
2222
draftRobotsAllowed?: boolean;
2323
}

packages/orm-models/src/models/page.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export interface InternalPage extends PublicPage {
1717
creatorId: string;
1818
draftTitle?: string;
1919
draftDescription?: string;
20-
draftSocialImage?: Media;
20+
draftSocialImage?: Media | null;
2121
draftRobotsAllowed?: boolean;
2222
}
2323

0 commit comments

Comments
 (0)