diff --git a/services/image/src/routes/ipfs.ts b/services/image/src/routes/ipfs.ts index d1f532c..913e43a 100644 --- a/services/image/src/routes/ipfs.ts +++ b/services/image/src/routes/ipfs.ts @@ -4,7 +4,11 @@ import { etag } from 'hono/etag' import { allowedOrigin } from '@kodadot/workers-utils' import { CACHE_DAY, CACHE_MONTH, Env } from '../utils/constants' import { fetchIPFS, toIpfsGw } from '../utils/ipfs' -import { getImageByPath, ipfsToCFI } from '../utils/cloudflare-images' +import { + getCFIFlexibleVariant, + getImageByPath, + ipfsToCFI, +} from '../utils/cloudflare-images' import type { ResponseType } from '../utils/types' const app = new Hono<{ Bindings: Env }>() @@ -12,7 +16,8 @@ const app = new Hono<{ Bindings: Env }>() app.use(etag()) app.use('/*', cors({ origin: allowedOrigin })) app.get('/*', async (c) => { - const { original } = c.req.query() + const query = c.req.query() + const { original } = query const isOriginal = original === 'true' const isHead = c.req.method === 'HEAD' @@ -48,7 +53,7 @@ app.get('/*', async (c) => { }) if (publicUrl) { - return c.redirect(publicUrl, 301) + return c.redirect(getCFIFlexibleVariant(query, publicUrl), 301) } } @@ -63,7 +68,7 @@ app.get('/*', async (c) => { }) if (imageUrl) { - return c.redirect(imageUrl, 301) + return c.redirect(getCFIFlexibleVariant(query, imageUrl), 301) } } diff --git a/services/image/src/tests/ipfs.test.ts b/services/image/src/tests/ipfs.test.ts index 0a4fdf2..d8b0043 100644 --- a/services/image/src/tests/ipfs.test.ts +++ b/services/image/src/tests/ipfs.test.ts @@ -143,3 +143,16 @@ test('ipfs - 200 - html', async () => { } `) }) + +test('ipfs - flexible variant - image ', async () => { + const res = await fetch( + 'https://image-beta.w.kodadot.xyz/ipfs/bafybeidv3wgydacgpre67lkciihrttvwl5nibzftxfppy6lfanjja4v7zm?w=100', + { redirect: 'manual' }, + ) + + const redirectURL = res.headers.get('location') + + expect(redirectURL).toBe( + 'https://imagedelivery.net/jk5b6spi_m_-9qC4VTnjpg/bafybeidv3wgydacgpre67lkciihrttvwl5nibzftxfppy6lfanjja4v7zm/w=100', + ) +}) diff --git a/services/image/src/tests/utils/cloudflare-images.test.ts b/services/image/src/tests/utils/cloudflare-images.test.ts new file mode 100644 index 0000000..c1029ba --- /dev/null +++ b/services/image/src/tests/utils/cloudflare-images.test.ts @@ -0,0 +1,14 @@ +import { expect, test } from 'vitest' +import { getCFIFlexibleVariant } from '../../utils/cloudflare-images' + +test('utils getCFIFlexibleVariant', async () => { + const url = + 'https://imagedelivery.net/jk5b6spi_m_-9qC4VTnjpg/bafybeiak6zlsmrhder2epl7qzxcpq6zqt6razp7wl66balp33nfno2fu7u/public' + + expect(getCFIFlexibleVariant({ w: '100' }, url).endsWith('/w=100')).toBe(true) + expect( + getCFIFlexibleVariant({ w: '100', blur: '50' }, url).endsWith( + '/w=100,blur=50', + ), + ).toBe(true) +}) diff --git a/services/image/src/utils/cloudflare-images.ts b/services/image/src/utils/cloudflare-images.ts index 1856e70..2867ef1 100644 --- a/services/image/src/utils/cloudflare-images.ts +++ b/services/image/src/utils/cloudflare-images.ts @@ -119,3 +119,41 @@ export async function getImageByPath({ return '' } + +const transformationParams = [ + 'w', + 'width', + 'h', + 'height', + 'anim', + 'background', + 'blur', + 'brightness', + 'fit', + 'fromat', + 'q', + 'quality', + 'sharpen', + 'trim.width', + 'trim.height', + 'trim.left', + 'trim.top', +] + +export function getCFIFlexibleVariant( + queryParams: { [param: string]: string }, + publicUrl: string, +): string { + const transformations = Object.keys(queryParams) + .filter((param) => transformationParams.includes(param)) + .map((param) => `${param}=${queryParams[param]}`) + .join(',') + + if (!transformations) { + return publicUrl + } + + publicUrl = publicUrl.split('/public')[0] + + return `${publicUrl}/${transformations}` +}