88 type ImageSize ,
99 SIZE_CONFIG ,
1010 CACHE_DURATION ,
11+ resolveRequestedImageSize ,
1112} from "@/lib/image-proxy-server" ;
1213
1314function getCacheControl ( request : NextRequest , localFile : boolean ) : string {
@@ -25,16 +26,20 @@ function getCacheControl(request: NextRequest, localFile: boolean): string {
2526 * Image Proxy API - Handles both local data paths and external image URLs
2627 *
2728 * Query parameters:
28- * - url: Image URL or local path (e.g., /data/2025/11/messages /discord/images/123.jpg) (required)
29+ * - url: Image URL or local path (e.g., /data/2025/11/channels /discord/images/123.jpg or /images/foo .jpg) (required)
2930 * - size: Optional size parameter (xs|sm|md|lg) for resizing
3031 *
31- * Example: /api/image-proxy?url=/data/2025/11/messages/discord/images/123.jpg&size=sm
32+ * Example: /api/image-proxy?url=/data/2025/11/channels/discord/images/123.jpg&size=sm
33+ * Example: /api/image-proxy?url=/images/chb-facade.avif&size=md
3234 * Example: /api/image-proxy?url=https://example.com/image.jpg&size=md
3335 */
3436export async function GET ( request : NextRequest ) {
3537 const { searchParams } = new URL ( request . url ) ;
3638 const url = searchParams . get ( "url" ) ;
37- const sizeParam = searchParams . get ( "size" ) as ImageSize | null ;
39+ const sizeParam = resolveRequestedImageSize (
40+ searchParams . get ( "size" ) ,
41+ searchParams . get ( "w" )
42+ ) ;
3843
3944 console . log ( "[image-proxy] Request - url:" , url , "size:" , sizeParam ) ;
4045
@@ -46,7 +51,7 @@ export async function GET(request: NextRequest) {
4651 }
4752
4853 // Prevent recursive proxying - reject if URL is already a proxy URL
49- if ( url . includes ( "/api/image-proxy" ) || url . includes ( "/api/discord-image-proxy" ) ) {
54+ if ( url . includes ( "/api/image-proxy" ) ) {
5055 console . log ( "[image-proxy] Rejected recursive proxy attempt:" , url ) ;
5156 return NextResponse . json (
5257 { error : "Cannot proxy a proxy URL" } ,
@@ -56,7 +61,17 @@ export async function GET(request: NextRequest) {
5661
5762 // Check if this is a local data path
5863 if ( url . startsWith ( "/data/" ) ) {
59- return handleLocalDataPath ( request , url , sizeParam ) ;
64+ return handleLocalPath ( request , url , sizeParam , {
65+ rootDir : DATA_DIR ,
66+ urlPrefix : "/data/" ,
67+ } ) ;
68+ }
69+
70+ if ( url . startsWith ( "/images/" ) ) {
71+ return handleLocalPath ( request , url , sizeParam , {
72+ rootDir : path . join ( process . cwd ( ) , "public" ) ,
73+ urlPrefix : "/" ,
74+ } ) ;
6075 }
6176
6277 // Otherwise, handle as external URL
@@ -66,27 +81,31 @@ export async function GET(request: NextRequest) {
6681}
6782
6883/**
69- * Handle local data directory paths
84+ * Handle local file paths rooted in a specific directory
7085 */
71- async function handleLocalDataPath (
86+ async function handleLocalPath (
7287 request : NextRequest ,
7388 localPath : string ,
74- sizeParam : ImageSize | null
89+ sizeParam : ImageSize | null ,
90+ options : {
91+ rootDir : string ;
92+ urlPrefix : string ;
93+ }
7594) : Promise < NextResponse > {
7695 try {
77- const dataDir = DATA_DIR ;
78- // Remove leading /data/ and construct absolute path
79- const relativePath = localPath . replace ( / ^ \/ d a t a \/ / , "" ) ;
80- const absolutePath = path . join ( dataDir , relativePath ) ;
96+ const { rootDir , urlPrefix } = options ;
97+ const normalizedPrefix = urlPrefix . endsWith ( "/" ) ? urlPrefix : ` ${ urlPrefix } /` ;
98+ const relativePath = localPath . replace ( new RegExp ( `^ ${ normalizedPrefix . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g , "\\$&" ) } ` ) , "" ) ;
99+ const absolutePath = path . join ( rootDir , relativePath ) ;
81100
82101 console . log ( "[image-proxy] Serving local file:" , absolutePath ) ;
83102
84- // Security check - ensure path is within data directory
85- const resolvedDataDir = path . resolve ( dataDir ) ;
103+ // Security check - ensure path is within the configured root directory
104+ const resolvedRootDir = path . resolve ( rootDir ) ;
86105 const resolvedPath = path . resolve ( absolutePath ) ;
87- if ( ! resolvedPath . startsWith ( resolvedDataDir ) ) {
106+ if ( ! resolvedPath . startsWith ( resolvedRootDir ) ) {
88107 console . log ( "[image-proxy] Security: Path outside data directory:" , resolvedPath ) ;
89- console . log ( "[image-proxy] Expected to be within:" , resolvedDataDir ) ;
108+ console . log ( "[image-proxy] Expected to be within:" , resolvedRootDir ) ;
90109 return NextResponse . json ( { error : "Invalid path" } , { status : 403 } ) ;
91110 }
92111
@@ -105,6 +124,7 @@ async function handleLocalDataPath(
105124 ".png" : "image/png" ,
106125 ".gif" : "image/gif" ,
107126 ".webp" : "image/webp" ,
127+ ".avif" : "image/avif" ,
108128 } ;
109129 let contentType = contentTypeMap [ ext ] || "image/jpeg" ;
110130
0 commit comments