diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 259e9e4..6eb00b6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -45,6 +45,39 @@ jobs: with: node-version: 18 + - name: Configure Cloudflare Images variants + run: | + curl -XPOST https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/images/v1/variants \ + -d '{ + "id": "avatar", + "options": { + "metadata": "copyright", + "width": 400, + "height": 400 + } + }' \ + -H 'Authorization: Bearer ${{ secrets.CF_API_TOKEN }}' + + curl -XPOST https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/images/v1/variants \ + -d '{ + "id": "header", + "options": { + "metadata": "copyright", + "width": 1500, + "height": 500 + } + }' \ + -H 'Authorization: Bearer ${{ secrets.CF_API_TOKEN }}' + + curl -XPOST https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/images/v1/variants \ + -d '{ + "id": "usercontent", + "options": { + "metadata": "copyright" + } + }' \ + -H 'Authorization: Bearer ${{ secrets.CF_API_TOKEN }}' + - name: Create D1 database uses: cloudflare/wrangler-action@2.0.0 with: @@ -192,7 +225,7 @@ jobs: echo "******" env: CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} - + - name: Create Queue uses: cloudflare/wrangler-action@2.0.0 with: @@ -227,7 +260,7 @@ jobs: curl https://api.cloudflare.com/client/v4/accounts/${{ secrets.CF_ACCOUNT_ID }}/pages/projects/wildebeest-${{ env.OWNER_LOWER }} \ -XPATCH \ -H 'Authorization: Bearer ${{ secrets.CF_API_TOKEN }}' \ - -d ' { + -d '{ "deployment_configs": { "production": { "queue_producers": { diff --git a/backend/src/media/image.ts b/backend/src/media/image.ts index c3f3e6d..70e41c6 100644 --- a/backend/src/media/image.ts +++ b/backend/src/media/image.ts @@ -19,7 +19,15 @@ type UploadResult = { uploaded: string } -export async function uploadImage(file: File, config: Config): Promise { +// https://docs.joinmastodon.org/user/profile/#avatar +const AVATAR_VARIANT = 'avatar' + +// https://docs.joinmastodon.org/user/profile/#header +const HEADER_VARIANT = 'header' + +const USER_CONTENT_VARIANT = 'usercontent' + +async function upload(file: File, config: Config): Promise { const formData = new FormData() const url = `https://api.cloudflare.com/client/v4/accounts/${config.accountId}/images/v1` @@ -43,7 +51,31 @@ export async function uploadImage(file: File, config: Config): Promise { throw new Error(`Cloudflare Images returned ${res.status}: ${body}`) } - // We assume there's only one variant for now. - const variant = data.result.variants[0] - return new URL(variant) + return data.result +} + +function selectVariant(res: UploadResult, name: string): URL { + for (let i = 0, len = res.variants.length; i < len; i++) { + const variant = res.variants[i] + if (variant.endsWith(`/${name}`)) { + return new URL(variant) + } + } + + throw new Error(`variant "${name}" not found`) +} + +export async function uploadAvatar(file: File, config: Config): Promise { + const result = await upload(file, config) + return selectVariant(result, AVATAR_VARIANT) +} + +export async function uploadHeader(file: File, config: Config): Promise { + const result = await upload(file, config) + return selectVariant(result, HEADER_VARIANT) +} + +export async function uploadUserContent(file: File, config: Config): Promise { + const result = await upload(file, config) + return selectVariant(result, USER_CONTENT_VARIANT) } diff --git a/backend/test/mastodon/accounts.spec.ts b/backend/test/mastodon/accounts.spec.ts index e3805fd..ca9721c 100644 --- a/backend/test/mastodon/accounts.spec.ts +++ b/backend/test/mastodon/accounts.spec.ts @@ -172,7 +172,10 @@ describe('Mastodon APIs', () => { JSON.stringify({ success: true, result: { - variants: ['https://example.com/' + file.name], + variants: [ + 'https://example.com/' + file.name + '/avatar', + 'https://example.com/' + file.name + '/header', + ], }, }) ) @@ -205,8 +208,8 @@ describe('Mastodon APIs', () => { assert.equal(res.status, 200) const data = await res.json() - assert.equal(data.avatar, 'https://example.com/selfie.jpg') - assert.equal(data.header, 'https://example.com/mountain.jpg') + assert.equal(data.avatar, 'https://example.com/selfie.jpg/avatar') + assert.equal(data.header, 'https://example.com/mountain.jpg/header') }) test('get remote actor by id', async () => { diff --git a/backend/test/mastodon/media.spec.ts b/backend/test/mastodon/media.spec.ts index a658ef4..6f394d5 100644 --- a/backend/test/mastodon/media.spec.ts +++ b/backend/test/mastodon/media.spec.ts @@ -19,7 +19,7 @@ describe('Mastodon APIs', () => { success: true, result: { id: 'abcd', - variants: ['https://example.com/' + file.name], + variants: ['https://example.com/' + file.name + '/usercontent'], }, }) ) diff --git a/functions/api/v1/accounts/update_credentials.ts b/functions/api/v1/accounts/update_credentials.ts index 7554b36..a715054 100644 --- a/functions/api/v1/accounts/update_credentials.ts +++ b/functions/api/v1/accounts/update_credentials.ts @@ -70,7 +70,7 @@ export async function handleRequest( const value = formData.get('avatar')! as any const config = { accountId, apiToken } - const url = await images.uploadImage(value, config) + const url = await images.uploadAvatar(value, config) await updateActorProperty(db, connectedActor.id, 'icon.url', url.toString()) } @@ -78,7 +78,7 @@ export async function handleRequest( const value = formData.get('header')! as any const config = { accountId, apiToken } - const url = await images.uploadImage(value, config) + const url = await images.uploadHeader(value, config) await updateActorProperty(db, connectedActor.id, 'image.url', url.toString()) } } diff --git a/functions/api/v2/media.ts b/functions/api/v2/media.ts index 7a23ed1..f87f799 100644 --- a/functions/api/v2/media.ts +++ b/functions/api/v2/media.ts @@ -28,7 +28,7 @@ export async function handleRequest( const file = formData.get('file')! as any const config = { accountId, apiToken } - const url = await media.uploadImage(file, config) + const url = await media.uploadUserContent(file, config) const properties = { url,