diff --git a/apps/api/src/routes/content/collections.ts b/apps/api/src/routes/content/collections.ts index a7698c5..b6219cd 100644 --- a/apps/api/src/routes/content/collections.ts +++ b/apps/api/src/routes/content/collections.ts @@ -1,8 +1,8 @@ import type { FastifyPluginAsync } from "fastify"; -import { env } from "@playfulprogramming/common"; import { db, posts } from "@playfulprogramming/db"; import { Type, type Static } from "typebox"; import { eq } from "drizzle-orm"; +import { createImageUrl } from "../../utils.ts"; const CollectionsQueryParamsSchema = Type.Object({ locale: Type.String({ default: "en" }), @@ -71,11 +71,6 @@ const CollectionsResponseSchema = Type.Array( type CollectionsResponse = Static; -function createImageUrl(path: string): string { - const s3PublicUrl = `${env.S3_PUBLIC_URL}/${env.S3_BUCKET}/`; - return new URL(path, s3PublicUrl).toString(); -} - const collectionsRoutes: FastifyPluginAsync = async (fastify) => { fastify.get<{ Querystring: Static; diff --git a/apps/api/src/routes/tasks/post-images.ts b/apps/api/src/routes/tasks/post-images.ts index 47c29cb..c79f993 100644 --- a/apps/api/src/routes/tasks/post-images.ts +++ b/apps/api/src/routes/tasks/post-images.ts @@ -1,5 +1,4 @@ import type { FastifyPluginAsync } from "fastify"; -import { env } from "@playfulprogramming/common"; import { db } from "@playfulprogramming/db"; import { Type, type Static } from "typebox"; import { @@ -7,6 +6,7 @@ import { Tasks, createJob, } from "@playfulprogramming/bullmq"; +import { createImageUrl } from "../../utils.ts"; const PostImageRequestSchema = Type.Object( { @@ -49,13 +49,10 @@ const PostImagesResponseSchema = Type.Object( function mapPostImages( result: PostImageOutput, ): Static { - const s3PublicUrl = `${env.S3_PUBLIC_URL}/${env.S3_BUCKET}/`; return { - banner: result.bannerKey - ? new URL(result.bannerKey, s3PublicUrl).toString() - : undefined, + banner: result.bannerKey ? createImageUrl(result.bannerKey) : undefined, linkPreview: result.linkPreviewKey - ? new URL(result.linkPreviewKey, s3PublicUrl).toString() + ? createImageUrl(result.linkPreviewKey) : undefined, }; } diff --git a/apps/api/src/routes/tasks/url-metadata.ts b/apps/api/src/routes/tasks/url-metadata.ts index 4513226..27ca3de 100644 --- a/apps/api/src/routes/tasks/url-metadata.ts +++ b/apps/api/src/routes/tasks/url-metadata.ts @@ -1,5 +1,4 @@ import type { FastifyPluginAsync } from "fastify"; -import { env } from "@playfulprogramming/common"; import { db } from "@playfulprogramming/db"; import { Type, type Static } from "typebox"; import { @@ -8,6 +7,7 @@ import { UrlMetadataInputSchema, createJob, } from "@playfulprogramming/bullmq"; +import { createImageUrl } from "../../utils.ts"; type UrlMetadataDbResult = NonNullable< Awaited> @@ -106,11 +106,6 @@ const UrlMetadataResponseSchema = Type.Object( }, ); -function mapObjectKey(key: string): string { - const s3PublicUrl = `${env.S3_PUBLIC_URL}/${env.S3_BUCKET}/`; - return new URL(key, s3PublicUrl).toString(); -} - function mapImageData( key: string | null, width: number | null, @@ -119,7 +114,7 @@ function mapImageData( ): Static | undefined { if (!key) return undefined; return { - src: mapObjectKey(key), + src: createImageUrl(key), width: width || undefined, height: height || undefined, altText: altText || undefined, @@ -143,7 +138,7 @@ async function mapEmbedGist( description: gist.description || undefined, files: gistFiles.map((file) => ({ filename: file.filename, - contentUrl: mapObjectKey(file.contentKey), + contentUrl: createImageUrl(file.contentKey), language: file.language, })), }; diff --git a/apps/api/src/utils.ts b/apps/api/src/utils.ts new file mode 100644 index 0000000..cced857 --- /dev/null +++ b/apps/api/src/utils.ts @@ -0,0 +1,16 @@ +import { env } from "@playfulprogramming/common"; + +/** Removes leading and trailing slashes from a string. */ +function trimSlashes(input: string): string { + return input.replace(/^\/+|\/+$/g, ""); +} + +const S3_BASE_URL = `${trimSlashes(env.S3_PUBLIC_URL)}/${trimSlashes(env.S3_BUCKET)}/`; +/** + * Constructs a full S3 image URL from a relative path. + * @param path The relative path to the image. + * @returns The absolute URL to the image. + */ +export function createImageUrl(path: string): string { + return new URL(trimSlashes(path), S3_BASE_URL).toString(); +}