Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove external category properly #1215

Merged
merged 4 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion web/api/v2/public/app/[app_id]/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { formatAppMetadata } from "@/api/helpers/app-store";
import { getAPIServiceGraphqlClient } from "@/api/helpers/graphql";
import { getAppStoreLocalisedCategoriesWithUrls } from "@/lib/categories";
import { NativeAppToAppIdMapping, NativeApps } from "@/lib/constants";
import { parseLocale } from "@/lib/languages";
import { AppStatsReturnType } from "@/lib/types";
Expand Down Expand Up @@ -136,13 +137,21 @@ export async function GET(
app_id: nativeAppItem.app_id,
};
}
const categories = getAppStoreLocalisedCategoriesWithUrls(locale);
const isCategoryValid = categories.some(
(category) => category?.id === formattedMetadata.category.id,
);

if (!isCategoryValid) {
return NextResponse.json({ error: "Invalid category" }, { status: 500 });
}

return NextResponse.json(
{ app_data: formattedMetadata },
{
status: 200,
headers: {
"Cache-Control": "public, max-age=86400, stale-if-error=86400",
"Cache-Control": "public, max-age=5, stale-if-error=86400",
},
},
);
Expand Down
36 changes: 33 additions & 3 deletions web/api/v2/public/apps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getAPIServiceGraphqlClient } from "@/api/helpers/graphql";
import { validateRequestSchema } from "@/api/helpers/validate-request-schema";
import {
AllCategory,
getAllLocalisedCategoriesWithUrls,
getAppStoreLocalisedCategoriesWithUrls,
getLocalisedCategory,
} from "@/lib/categories";
import { NativeApps } from "@/lib/constants";
Expand Down Expand Up @@ -32,6 +32,7 @@ const queryParamsSchema = yup.object({
.oneOf(["mini-app", "external", "native"])
.notRequired(),
override_country: yup.string().notRequired(),
show_external: yup.boolean().notRequired().default(false),
});

export const GET = async (request: NextRequest) => {
Expand Down Expand Up @@ -132,6 +133,15 @@ export const GET = async (request: NextRequest) => {
});
}

if (!parsedParams.show_external) {
topApps = topApps.filter(
(app) => app.category.toLowerCase() !== "external",
);
highlightsApps = highlightsApps.filter(
(app) => app.category.toLowerCase() !== "external",
);
}

// ANCHOR: Filter top apps by country
if (country && topApps.length > 0) {
topApps = topApps.filter((app) =>
Expand Down Expand Up @@ -212,6 +222,26 @@ export const GET = async (request: NextRequest) => {
return aIndex - bIndex;
});

// validate all apps have valid categories
const categories = getAppStoreLocalisedCategoriesWithUrls(locale);
const areAppCategoriesValid =
formattedTopApps.every((app) =>
categories.some((category) => category?.id === app.category.id),
) &&
highlightedApps.every((app) =>
categories.some((category) => category?.id === app.category.id),
);

if (!areAppCategoriesValid) {
return errorResponse({
statusCode: 500,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure the proper alert is set up. If this throws it will not alert us currently

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code: "invalid_categories",
detail: "Some apps have invalid categories",
attribute: null,
req: request,
});
}

return NextResponse.json(
{
app_rankings: {
Expand All @@ -222,13 +252,13 @@ export const GET = async (request: NextRequest) => {
...AllCategory,
name: getLocalisedCategory(AllCategory.name, locale).name,
},
categories: getAllLocalisedCategoriesWithUrls(locale), // TODO: Localise
categories,
},
{
headers: {
// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html#ExpirationDownloadDist
// https://aws.amazon.com/about-aws/whats-new/2023/05/amazon-cloudfront-stale-while-revalidate-stale-if-error-cache-control-directives/
"Cache-Control": "public, max-age=86400, stale-if-error=86400",
"Cache-Control": "public, max-age=5, stale-if-error=86400",
},
},
);
Expand Down
7 changes: 5 additions & 2 deletions web/lib/categories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,13 @@ export const getLocalisedCategory = (
};
};

export const getAllLocalisedCategoriesWithUrls = (locale: string) => {
export const getAppStoreLocalisedCategoriesWithUrls = (locale: string) => {
const defaultLocale = locale || "en";
return Categories.map((category) => {
if (category.id === "external") {
return null;
}
const { id, name } = getLocalisedCategory(category.name, defaultLocale);
return { id, name, icon_url: category.icon_url };
});
}).filter((category) => category !== null);
};
60 changes: 60 additions & 0 deletions web/tests/api/v2/public/apps/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -679,4 +679,64 @@ describe("/api/public/app/[app_id]", () => {
expect(data.error).toBe("Draft already verified");
});
});
describe("response integrity", () => {
test("should return 500 when category is invalid", async () => {
jest.mocked(getAppMetadataSdk).mockImplementation(() => ({
GetAppMetadata: jest.fn().mockResolvedValue({
app_metadata: [
{
id: "1",
name: "Example App",
app_id: "test-app",
short_name: "test",
logo_img_url: "logo.png",
showcase_img_urls: ["showcase1.png", "showcase2.png"],
hero_image_url: "hero.png",
world_app_description:
"This is an example app designed to showcase the capabilities of our platform.",
world_app_button_text: "Use Integration",
category: "INVALID!!",
description:
'{"description_overview":"fewf","description_how_it_works":"few","description_connect":"fewf"}',
integration_url: "https://example.com/integration",
app_website_url: "https://example.com",
source_code_url: "https://github.com/example/app",
whitelisted_addresses: ["0x1234", "0x5678"],
app_mode: "mini-app",
support_link: "[email protected]",
supported_countries: ["us"],
associated_domains: ["https://worldcoin.org"],
contracts: ["0x0c892815f0B058E69987920A23FBb33c834289cf"],
permit2_tokens: ["0x0c892815f0B058E69987920A23FBb33c834289cf"],
supported_languages: ["en", "es"],
is_reviewer_world_app_approved: true,
verification_status: "verified",
is_allowed_unlimited_notifications: false,
max_notifications_per_day: 10,
app: {
team: {
name: "Example Team",
},
rating_sum: 10,
rating_count: 3,
},
},
],
}),
}));

const request = new NextRequest(
"https://cdn.test.com/api/public/app/test-app",
{
headers: {
host: "cdn.test.com",
},
},
);
const response = await GET(request, { params: { app_id: "test-app" } });
expect(response.status).toBe(500);
const data = await response.json();
expect(data).toEqual({ error: "Invalid category" });
});
});
});
71 changes: 68 additions & 3 deletions web/tests/api/v2/public/apps/apps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ describe("/api/v2/public/apps", () => {
expect(await response.json()).toEqual({
app_rankings: { top_apps: [], highlights: [] },
all_category: AllCategory,
categories: Categories,
categories: Categories.filter((category) => category.id !== "external"),
});
});

Expand Down Expand Up @@ -573,8 +573,8 @@ describe("/api/v2/public/apps", () => {
},
],
},
categories: Categories.filter((category) => category.id !== "external"),
all_category: AllCategory,
categories: Categories,
});
});

Expand Down Expand Up @@ -685,8 +685,73 @@ describe("/api/v2/public/apps", () => {
],
highlights: [],
},
categories: Categories.filter((category) => category.id !== "external"),
all_category: AllCategory,
categories: Categories,
});
});

test("Error on invalid category", async () => {
jest.mocked(getWebHighlightsSdk).mockImplementation(() => ({
GetHighlights: jest.fn().mockResolvedValue({
app_rankings: [{ rankings: [] }],
}),
}));
jest.mocked(getHighlightsSdk).mockImplementation(() => ({
GetHighlights: jest.fn().mockResolvedValue({
highlights: [],
}),
}));

jest.mocked(getAppsSdk).mockImplementation(() => ({
GetApps: jest.fn().mockResolvedValue({
top_apps: [
{
name: "Example App",
app_id: "app_test_123",
short_name: "test",
logo_img_url: "logo.png",
showcase_img_urls: ["showcase1.png", "showcase2.png"],
hero_image_url: "hero.png",
world_app_description:
"This is an example app designed to showcase the capabilities of our platform.",
world_app_button_text: "Use Integration",
category: "INVALID!!",
description:
'{"description_overview":"fewf","description_how_it_works":"few","description_connect":"fewf"}',
integration_url: "https://example.com/integration",
app_website_url: "https://example.com",
source_code_url: "https://github.com/example/app",
whitelisted_addresses: ["0x1234", "0x5678"],
app_mode: "mini-app",
support_link: "[email protected]",
associated_domains: ["https://worldcoin.org"],
contracts: ["0x0c892815f0B058E69987920A23FBb33c834289cf"],
permit2_tokens: ["0x0c892815f0B058E69987920A23FBb33c834289cf"],
supported_countries: ["us"],
supported_languages: ["en", "es"],
verification_status: "verified",
is_allowed_unlimited_notifications: false,
max_notifications_per_day: 10,
app: {
team: {
name: "Example Team",
},
rating_sum: 10,
rating_count: 3,
},
},
],
}),
}));

const request = new NextRequest("https://cdn.test.com/api/v2/public/apps", {
headers: {
host: "cdn.test.com",
},
});

const response = await GET(request);

expect(response.status).toEqual(500);
});
});
Loading