-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature: proxy files with custom slugs #224
base: main
Are you sure you want to change the base?
Changes from all commits
51503d7
bc17e17
210264b
7277ced
e5b0bab
d40ec53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import type { Client } from '@datocms/cli/lib/cma-client-node'; | ||
|
||
export default async function (client: Client) { | ||
console.log('Creating new fields/fieldsets'); | ||
|
||
console.log( | ||
'Create Single-line string field "Slug" (`slug`) in model "\uD83D\uDCE6 File" (`file`)' | ||
); | ||
await client.fields.create('GjWw8t-hTFaYYWyc53FeIg', { | ||
id: 'cnnDYybJTAGvHxiYq2MJ8g', | ||
label: 'Slug', | ||
field_type: 'string', | ||
api_key: 'slug', | ||
hint: 'Optional custom slug, like <code>/my-files/example.pdf</code>. This may be useful if you need files to be available under a specific URL.', | ||
validators: { | ||
format: { | ||
custom_pattern: '^/.*', | ||
description: 'Field must start with a forward slash: /', | ||
}, | ||
}, | ||
appearance: { | ||
addons: [], | ||
editor: 'single_line', | ||
parameters: { heading: false, placeholder: null }, | ||
}, | ||
default_value: '', | ||
}); | ||
|
||
console.log('Update existing fields/fieldsets'); | ||
|
||
console.log( | ||
'Update Single-line string field "Slug" (`slug`) in model "\uD83D\uDCE6 File" (`file`)' | ||
); | ||
await client.fields.update('cnnDYybJTAGvHxiYq2MJ8g', { position: 3 }); | ||
|
||
console.log( | ||
'Update Single-line string field "Title" (`title`) in model "\uD83D\uDCE6 File" (`file`)' | ||
); | ||
await client.fields.update('YIftd04cTlyz0aEvqsfWXA', { | ||
appearance: { | ||
addons: [], | ||
editor: 'EiyZ3d0SSDCPCNbsKBIwWQ', | ||
parameters: { | ||
defaultFunction: | ||
'if (slug) {\n return slug.split(\'/\').pop()\n}\nconst upload = await getUpload(file.upload_id)\nreturn `${upload.filename}`', | ||
}, | ||
field_extension: 'computedFields', | ||
}, | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { writeFile } from 'node:fs/promises'; | ||
import { buildClient } from '@datocms/cma-client-node'; | ||
import dotenv from 'dotenv-safe'; | ||
import { datocmsEnvironment } from '../datocms-environment'; | ||
|
||
dotenv.config({ | ||
allowEmptyValues: Boolean(process.env.CI), | ||
}); | ||
|
||
type FileRecord = { | ||
slug?: string; | ||
file: { | ||
upload_id: string; | ||
} | ||
} | ||
type FileSlugMap = { | ||
[key: string]: string; | ||
}; | ||
|
||
async function getFileProxyMap() { | ||
// use client instead of http api for pagination support | ||
const client = buildClient({ | ||
apiToken: process.env.DATOCMS_READONLY_API_TOKEN!, | ||
environment: datocmsEnvironment, | ||
}); | ||
|
||
const fileSlugMap = {} as FileSlugMap; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
for await (const item of client.items.listPagedIterator({ filter: { type: 'file' } }) as unknown as FileRecord[]) { | ||
if (item.slug) { | ||
const asset = await client.uploads.find(item.file.upload_id); | ||
if (asset) { | ||
fileSlugMap[item.slug] = asset.url; | ||
} | ||
} | ||
} | ||
return fileSlugMap; | ||
} | ||
|
||
async function downloadFileProxyMap() { | ||
const files = await getFileProxyMap(); | ||
await writeFile('./src/lib/routing/file-proxy-map.json', JSON.stringify(files, null, 2)); | ||
} | ||
|
||
downloadFileProxyMap() | ||
.then(() => console.log('File slugs downloaded')); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,5 +10,6 @@ fragment FileRoute on FileRecord { | |
url | ||
} | ||
locale | ||
customSlug: slug | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. required to resolve clashing types with other There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would also be resolved when renaming the field |
||
title | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import type { FileRouteFragment } from '@lib/datocms/types'; | ||
import { datocmsAssetsOrigin } from '@lib/datocms'; | ||
import fileProxyMapUntyped from './file-proxy-map.json'; | ||
|
||
export const getFileHref = (record: FileRouteFragment) => { | ||
if (record.customSlug) { | ||
return record.customSlug; | ||
} | ||
|
||
return record.file.url.replace(datocmsAssetsOrigin, '/files/'); | ||
}; | ||
|
||
type FileProxyMap = { | ||
[key: string]: string; | ||
}; | ||
const fileProxyMap = fileProxyMapUntyped as FileProxyMap; | ||
|
||
export const getFileUrlBySlug = (slug: string) => { | ||
return fileProxyMap[slug]; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { defineMiddleware } from 'astro/middleware'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using the new atomic middleware handlers setup introduced in #222 . Should maybe be merged first? |
||
import { getFileUrlBySlug } from '@lib/routing/file'; | ||
|
||
/** | ||
* Proxy files middleware: | ||
* If there is no matching route (404) and there is a file with a matching custom slug, | ||
* proxy the file from DatoCMS assets. | ||
*/ | ||
export const proxyFiles = defineMiddleware(async ({ request }, next) => { | ||
const originalResponse = await next(); | ||
|
||
// only process 404 responses | ||
if (originalResponse.status !== 404) { | ||
return originalResponse; | ||
} | ||
|
||
const { pathname } = new URL(request.url); | ||
const fileUrl = getFileUrlBySlug(pathname); | ||
if (!fileUrl) { | ||
return originalResponse; | ||
} | ||
|
||
const fileResponse = await fetch(fileUrl); | ||
if (!fileResponse.ok) { | ||
return originalResponse; | ||
} | ||
|
||
// Astro forces the original 404 status unless we use `X-Astro-Rewrite: true` | ||
// @see https://github.com/withastro/roadmap/discussions/665#discussioncomment-6831528 | ||
return new Response(fileResponse.body, { | ||
...fileResponse, | ||
headers: { | ||
...fileResponse.headers, | ||
'X-Astro-Rewrite': 'true', | ||
}, | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think naming this slug is confusing. In my perception, a slug is a human-readable, yet machine-safe path fragment. What is described in the hint, and in the
customFields
function seems to show a path, not a slug