Skip to content

Commit

Permalink
remove external category properly (#1215)
Browse files Browse the repository at this point in the history
* remove external category properly

* category validation

* typefixes
  • Loading branch information
michalstruck authored Feb 27, 2025
1 parent d57c644 commit 3ac0107
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 9 deletions.
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,
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);
});
});

0 comments on commit 3ac0107

Please sign in to comment.