Direct R2 assets

This commit is contained in:
2026-06-21 08:14:57 -04:00
parent 72731306ff
commit 99a6a294ba
6 changed files with 104 additions and 26 deletions
+55 -8
View File
@@ -35,6 +35,13 @@ const directusUrl = process.env.DIRECTUS_URL ?? 'http://directus:8055';
const directusPublicUrl = process.env.DIRECTUS_PUBLIC_URL && !process.env.DIRECTUS_PUBLIC_URL.includes('example.com')
? process.env.DIRECTUS_PUBLIC_URL
: directusUrl;
const directusAssetBaseUrl = process.env.DIRECTUS_ASSET_BASE_URL && !process.env.DIRECTUS_ASSET_BASE_URL.includes('example.com')
? process.env.DIRECTUS_ASSET_BASE_URL
: undefined;
const directusAssetUrlTemplate =
process.env.DIRECTUS_ASSET_URL_TEMPLATE && !process.env.DIRECTUS_ASSET_URL_TEMPLATE.includes('example.com')
? process.env.DIRECTUS_ASSET_URL_TEMPLATE
: undefined;
const directusToken = process.env.DIRECTUS_ADMIN_TOKEN;
const homepageBannerFolder = process.env.DIRECTUS_HOMEPAGE_BANNER_FOLDER ?? 'homepage-banners';
const cafePageFolder = process.env.DIRECTUS_CAFE_PAGE_FOLDER ?? 'cafe-page';
@@ -49,6 +56,7 @@ type DirectusFile = {
title?: string;
description?: string;
filename_download?: string;
filename_disk?: string;
type?: string;
};
@@ -57,8 +65,8 @@ type EventTemplateRecord = {
title?: string;
slug?: string;
description?: string;
image?: string;
logo?: string;
image?: string | DirectusFile;
logo?: string | DirectusFile;
booking_url?: string;
};
@@ -113,7 +121,46 @@ async function readDirectusEndpoint<T>(endpoint: URL): Promise<T[]> {
return payload.data ?? [];
}
export function resolveDirectusAssetUrl(fileId: string): string {
function extensionFromFilename(filename?: string): string {
if (!filename) return '';
const lastSegment = filename.split('/').pop() ?? '';
const dotIndex = lastSegment.lastIndexOf('.');
if (dotIndex <= 0 || dotIndex === lastSegment.length - 1) return '';
return lastSegment.slice(dotIndex);
}
function directusFileId(file: string | DirectusFile): string {
return typeof file === 'string' ? file : file.id;
}
function directusObjectKey(file: string | DirectusFile): string {
if (typeof file !== 'string' && file.filename_disk) {
return file.filename_disk;
}
const fileId = directusFileId(file);
return `${fileId}${extensionFromFilename(typeof file === 'string' ? undefined : file.filename_download)}`;
}
export function resolveDirectusAssetUrl(file: string | DirectusFile): string {
const fileId = directusFileId(file);
const r2ObjectKey = directusObjectKey(file);
const encodedObjectKey = encodeURIComponent(r2ObjectKey);
if (directusAssetUrlTemplate) {
return directusAssetUrlTemplate
.replaceAll('{fileId}', encodedObjectKey)
.replaceAll('{id}', encodedObjectKey)
.replaceAll('{key}', encodedObjectKey);
}
if (directusAssetBaseUrl) {
const baseUrl = directusAssetBaseUrl.endsWith('/') ? directusAssetBaseUrl : `${directusAssetBaseUrl}/`;
return new URL(encodedObjectKey, baseUrl).toString();
}
return new URL(`/assets/${fileId}`, directusPublicUrl).toString();
}
@@ -137,11 +184,11 @@ async function getImagesFromFolder(folderName: string, fallbackImages: HomepageB
endpoint.searchParams.set('sort', '-uploaded_on');
endpoint.searchParams.set('filter[folder][_eq]', folder.id);
endpoint.searchParams.set('filter[type][_starts_with]', 'image/');
endpoint.searchParams.set('fields', 'id,title,description,filename_download,type');
endpoint.searchParams.set('fields', 'id,title,description,filename_download,filename_disk,type');
const files = await readDirectusEndpoint<DirectusFile>(endpoint);
const images = files.map((file) => ({
src: resolveDirectusAssetUrl(file.id),
src: resolveDirectusAssetUrl(file),
alt: file.description || file.title || file.filename_download || 'Swansea Airport',
}));
@@ -195,9 +242,9 @@ async function getRecurringEvents(): Promise<EventItem[]> {
const endpoint = new URL('/items/event_dates', directusUrl);
endpoint.searchParams.set('limit', '100');
endpoint.searchParams.set('sort', 'date');
endpoint.searchParams.set(
'fields',
'id,date,template.id,template.title,template.slug,template.description,template.image,template.logo,template.booking_url',
endpoint.searchParams.set(
'fields',
'id,date,template.id,template.title,template.slug,template.description,template.image.id,template.image.filename_download,template.image.filename_disk,template.logo.id,template.logo.filename_download,template.logo.filename_disk,template.booking_url',
);
const eventDates = await readDirectusEndpoint<EventDateRecord>(endpoint);
+2 -2
View File
@@ -27,8 +27,8 @@ export type EventItem = {
end_datetime?: string;
location_text?: string;
registration_link?: string;
realimage?: string | { id?: string; filename_download?: string; title?: string };
logo?: string | { id?: string; filename_download?: string; title?: string };
realimage?: string | { id?: string; filename_download?: string; filename_disk?: string; title?: string };
logo?: string | { id?: string; filename_download?: string; filename_disk?: string; title?: string };
status?: string;
is_featured?: boolean;
tags?: string[];
+17 -16
View File
@@ -1,40 +1,41 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getEvents } from '../../lib/directus';
import { getEvents, resolveDirectusAssetUrl } from '../../lib/directus';
import { getUpcomingEvents } from '../../lib/events';
import { formatDate, formatDateTime } from '../../lib/format';
import { normalizeSlug } from '../../lib/slug';
type EventItem = Awaited<ReturnType<typeof getEvents>>[number];
declare const process: {
env: Record<string, string | undefined>;
};
function resolveEventImageSource(realimage: EventItem['realimage']): string | null {
if (!realimage) return null;
const candidate = typeof realimage === 'string' ? realimage : realimage.id;
if (!candidate) return null;
const configuredDirectusPublicUrl = process.env.DIRECTUS_PUBLIC_URL;
const directusPort = process.env.DIRECTUS_PORT ?? '8066';
const localFallbackUrl = `${Astro.url.protocol}//${Astro.url.hostname}:${directusPort}`;
const directusBaseUrl =
configuredDirectusPublicUrl && !configuredDirectusPublicUrl.includes('example.com')
? configuredDirectusPublicUrl
: localFallbackUrl;
if (typeof realimage !== 'string' && realimage.id) {
return resolveDirectusAssetUrl({
id: realimage.id,
filename_download: realimage.filename_download,
});
}
if (candidate.startsWith('/')) {
return new URL(candidate, directusBaseUrl).toString();
if (candidate.startsWith('/assets/')) {
return resolveDirectusAssetUrl(candidate.slice('/assets/'.length).split('/')[0]);
}
if (candidate.startsWith('http://') || candidate.startsWith('https://')) {
const candidateUrl = new URL(candidate);
return candidateUrl.pathname.startsWith('/assets/') ? new URL(candidateUrl.pathname, directusBaseUrl).toString() : candidate;
return candidateUrl.pathname.startsWith('/assets/')
? resolveDirectusAssetUrl(candidateUrl.pathname.slice('/assets/'.length).split('/')[0])
: candidate;
}
return new URL(`/assets/${candidate}`, directusBaseUrl).toString();
if (candidate.startsWith('/')) {
return candidate;
}
return resolveDirectusAssetUrl(candidate);
}
export async function getStaticPaths() {