Skip to content
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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ STELLAR_NETWORK=testnet
CONTRACT_ADDRESS=
BACKEND_URL=http://localhost:3001
JWT_SECRET=your_jwt_secret_key
NEXT_PUBLIC_IMAGE_REMOTE_PATTERNS=https://cdn.myfans.app,https://media.myfans.app

# -----------------------------------------------------------------------------
# CORS Configuration (for frontend reference)
Expand Down
5 changes: 2 additions & 3 deletions frontend/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import type { NextConfig } from "next";
import { getRemoteImagePatterns } from "./src/lib/image-remote-patterns";

const nextConfig: NextConfig = {
turbopack: {
root: process.cwd(),
},
images: {
remotePatterns: [
{ protocol: 'https', hostname: '**' },
],
remotePatterns: getRemoteImagePatterns(),
},
// Ensure proper metadata handling for SSR/SSG
experimental: {
Expand Down
39 changes: 39 additions & 0 deletions frontend/src/lib/image-remote-patterns.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { describe, expect, it } from "vitest";
import { getRemoteImagePatterns } from "./image-remote-patterns";

describe("getRemoteImagePatterns", () => {
it("returns localhost defaults when no env override is configured", () => {
expect(getRemoteImagePatterns(undefined)).toEqual([
{ protocol: "http", hostname: "localhost", pathname: "/**" },
{ protocol: "http", hostname: "127.0.0.1", pathname: "/**" },
]);
});

it("parses comma-separated hostnames as https image hosts", () => {
expect(getRemoteImagePatterns("cdn.myfans.app, media.myfans.app")).toEqual([
{ protocol: "http", hostname: "localhost", pathname: "/**" },
{ protocol: "http", hostname: "127.0.0.1", pathname: "/**" },
{ protocol: "https", hostname: "cdn.myfans.app", pathname: "/**" },
{ protocol: "https", hostname: "media.myfans.app", pathname: "/**" },
]);
});

it("parses full URLs with protocol, port, and path", () => {
expect(
getRemoteImagePatterns("https://cdn.myfans.app/assets, http://localhost:4000/uploads"),
).toEqual([
{ protocol: "http", hostname: "localhost", pathname: "/**" },
{ protocol: "http", hostname: "127.0.0.1", pathname: "/**" },
{ protocol: "https", hostname: "cdn.myfans.app", pathname: "/assets/**" },
{ protocol: "http", hostname: "localhost", port: "4000", pathname: "/uploads/**" },
]);
});

it("deduplicates repeated entries", () => {
expect(getRemoteImagePatterns("localhost,cdn.myfans.app,cdn.myfans.app")).toEqual([
{ protocol: "http", hostname: "localhost", pathname: "/**" },
{ protocol: "http", hostname: "127.0.0.1", pathname: "/**" },
{ protocol: "https", hostname: "cdn.myfans.app", pathname: "/**" },
]);
});
});
59 changes: 59 additions & 0 deletions frontend/src/lib/image-remote-patterns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { RemotePattern } from "next/dist/shared/lib/image-config";

const DEFAULT_REMOTE_IMAGE_HOSTS = ["http://localhost", "http://127.0.0.1"];
const DEFAULT_PROTOCOL = "https";

function normalizeEntry(entry: string): string | null {
const trimmed = entry.trim();
return trimmed.length > 0 ? trimmed : null;
}

function toPattern(entry: string): RemotePattern {
if (entry === "localhost" || entry === "127.0.0.1") {
return {
protocol: "http",
hostname: entry,
pathname: "/**",
};
}

if (entry.includes("://")) {
const url = new URL(entry);

return {
protocol: url.protocol.replace(":", "") as RemotePattern["protocol"],
hostname: url.hostname,
...(url.port ? { port: url.port } : {}),
pathname: url.pathname && url.pathname !== "/" ? `${url.pathname.replace(/\/$/, "")}/**` : "/**",
};
}

return {
protocol: DEFAULT_PROTOCOL,
hostname: entry,
pathname: "/**",
};
}

export function getRemoteImagePatterns(
rawValue = process.env.NEXT_PUBLIC_IMAGE_REMOTE_PATTERNS,
): RemotePattern[] {
const configuredEntries =
rawValue
?.split(",")
.map(normalizeEntry)
.filter((entry): entry is string => entry !== null) ?? [];

const uniqueEntries = [...new Set([...DEFAULT_REMOTE_IMAGE_HOSTS, ...configuredEntries])];
const uniquePatterns = new Map<string, RemotePattern>();

for (const entry of uniqueEntries) {
const pattern = toPattern(entry);
const key = [pattern.protocol, pattern.hostname, pattern.port ?? "", pattern.pathname ?? ""].join("|");
if (!uniquePatterns.has(key)) {
uniquePatterns.set(key, pattern);
}
}

return [...uniquePatterns.values()];
}
Loading