finally fully working image uploads

This commit is contained in:
keyan 2024-03-12 18:51:58 -05:00
parent 72ecc7b266
commit 8a96fbb4d0
6 changed files with 43 additions and 16 deletions

View File

@ -58,7 +58,7 @@ LND_SOCKET=sn_lnd:10009
NOSTR_PRIVATE_KEY=5f30b7e7714360f51f2be2e30c1d93b7fdf67366e730658e85777dfcc4e4245f
# imgproxy
NEXT_PUBLIC_IMGPROXY_URL=http://imgproxy:3001
NEXT_PUBLIC_IMGPROXY_URL=http://localhost:3001
IMGPROXY_KEY=9c273e803fd5d444bf8883f8c3000de57bee7995222370cab7f2d218dd9a4bbff6ca11cbf902e61eeef4358616f231da51e183aee6841e3a797a5c9a9530ba67
IMGPROXY_SALT=47b802be2c9250a66b998f411fc63912ab0bc1c6b47d99b8d37c61019d1312a984b98745eac83db9791b01bb8c93ecbc9b2ef9f2981d66061c7d0a4528ff6465
@ -74,8 +74,8 @@ IMGPROXY_DOWNLOAD_TIMEOUT=9
# IMGPROXY_ENABLE_DEBUG_HEADERS=true
NEXT_PUBLIC_AWS_UPLOAD_BUCKET=uploads
NEXT_PUBLIC_MEDIA_DOMAIN=s3:4566
NEXT_PUBLIC_MEDIA_URL=http://s3:4566/uploads
NEXT_PUBLIC_MEDIA_DOMAIN=localhost:4566
NEXT_PUBLIC_MEDIA_URL=http://localhost:4566/uploads
# search
OPENSEARCH_URL=http://opensearch:9200
@ -91,6 +91,10 @@ DATABASE_URL="postgresql://sn:password@db:5432/stackernews?schema=public"
# FOR DOCKER ONLY #
###################
# containers can't use localhost, so we need to use the container name
IMGPROXY_URL_DOCKER=http://imgproxy:8080
MEDIA_URL_DOCKER=http://s3:4566/uploads
# postgres container stuff
POSTGRES_PASSWORD=password
POSTGRES_USER=sn

View File

@ -8,11 +8,17 @@ AWS.config.update({
region: bucketRegion
})
const config = {
apiVersion: '2006-03-01',
endpoint: process.env.NODE_ENV === 'development' ? `${MEDIA_URL}` : undefined,
s3ForcePathStyle: process.env.NODE_ENV === 'development'
}
export function createPresignedPost ({ key, type, size }) {
const s3 = new AWS.S3({
apiVersion: '2006-03-01',
endpoint: process.env.NODE_ENV === 'development' ? `${MEDIA_URL}/${Bucket}` : undefined,
s3ForcePathStyle: process.env.NODE_ENV === 'development'
...config,
// in development, we need to be able to call this from localhost
endpoint: process.env.NODE_ENV === 'development' ? `${process.env.NEXT_PUBLIC_MEDIA_URL}` : undefined
})
return new Promise((resolve, reject) => {
s3.createPresignedPost({
@ -30,7 +36,7 @@ export function createPresignedPost ({ key, type, size }) {
}
export async function deleteObjects (keys) {
const s3 = new AWS.S3({ apiVersion: '2006-03-01' })
const s3 = new AWS.S3(config)
// max 1000 keys per request
// see https://docs.aws.amazon.com/cli/latest/reference/s3api/delete-objects.html
const batchSize = 1000

View File

@ -68,6 +68,10 @@ function ImageProxy ({ src, srcSet: { dimensions, ...srcSetObj } = {}, onClick,
if (!srcSetObj) return undefined
// srcSetObj shape: { [widthDescriptor]: <imgproxyUrl>, ... }
return Object.entries(srcSetObj).reduce((acc, [wDescriptor, url], i, arr) => {
// backwards compatibility: we used to replace image urls with imgproxy urls rather just storing paths
if (!url.startsWith('http')) {
url = `${process.env.NEXT_PUBLIC_IMGPROXY_URL}${url}`
}
return acc + `${url} ${wDescriptor}` + (i < arr.length - 1 ? ', ' : '')
}, '')
}, [srcSetObj])
@ -77,6 +81,9 @@ function ImageProxy ({ src, srcSet: { dimensions, ...srcSetObj } = {}, onClick,
const bestResSrc = useMemo(() => {
if (!srcSetObj) return src
return Object.entries(srcSetObj).reduce((acc, [wDescriptor, url]) => {
if (!url.startsWith('http')) {
url = `${process.env.NEXT_PUBLIC_IMGPROXY_URL}${url}`
}
const w = Number(wDescriptor.replace(/w$/, ''))
return w > acc.w ? { w, url } : acc
}, { w: 0, url: undefined }).url

View File

@ -100,6 +100,8 @@ services:
- .env.development
ports:
- "3001:8080"
expose:
- "8080"
labels:
- "CONNECT=localhost:3001"
s3:
@ -118,6 +120,8 @@ services:
- DEBUG=1
ports:
- "4566:4566"
expose:
- "4566"
volumes:
- 's3:/var/lib/localstack'
- './docker/s3/init-s3.sh:/etc/localstack/init/ready.d/init-s3.sh'

View File

@ -10,8 +10,8 @@ export const BOOST_MIN = BOOST_MULT * 5
export const UPLOAD_SIZE_MAX = 25 * 1024 * 1024
export const UPLOAD_SIZE_MAX_AVATAR = 5 * 1024 * 1024
export const IMAGE_PIXELS_MAX = 35000000
// backwards compatibile with old media domain env var
export const MEDIA_URL = process.env.NEXT_PUBLIC_MEDIA_URL || `https://${process.env.NEXT_PUBLIC_MEDIA_DOMAIN}`
// backwards compatibile with old media domain env var and precedence for docker url if set
export const MEDIA_URL = process.env.MEDIA_URL_DOCKER || process.env.NEXT_PUBLIC_MEDIA_URL || `https://${process.env.NEXT_PUBLIC_MEDIA_DOMAIN}`
export const AWS_S3_URL_REGEXP = new RegExp(`${MEDIA_URL}/([0-9]+)`, 'g')
export const UPLOAD_TYPES_ALLOW = [
'image/gif',

View File

@ -10,7 +10,7 @@ if (!imgProxyEnabled) {
console.warn('IMGPROXY_* env vars not set, imgproxy calls are no-ops now')
}
const IMGPROXY_URL = process.env.NEXT_PUBLIC_IMGPROXY_URL
const IMGPROXY_URL = process.env.IMGPROXY_URL_DOCKER || process.env.NEXT_PUBLIC_IMGPROXY_URL
const IMGPROXY_SALT = process.env.IMGPROXY_SALT
const IMGPROXY_KEY = process.env.IMGPROXY_KEY
@ -89,6 +89,12 @@ export const createImgproxyUrls = async (id, text, { models, forceFetch }) => {
const imgproxyUrls = {}
for (let url of urls) {
if (!url) continue
let fetchUrl = url
if (process.env.MEDIA_URL_DOCKER) {
console.log('[imgproxy] id:', id, '-- replacing media url:', url)
fetchUrl = url.replace(process.env.NEXT_PUBLIC_MEDIA_URL, process.env.MEDIA_URL_DOCKER)
console.log('[imgproxy] id:', id, '-- with:', fetchUrl)
}
console.log('[imgproxy] id:', id, '-- processing url:', url)
if (url.startsWith(IMGPROXY_URL)) {
@ -97,17 +103,17 @@ export const createImgproxyUrls = async (id, text, { models, forceFetch }) => {
url = decodeOriginalUrl(url)
console.log('[imgproxy] id:', id, '-- original url:', url)
}
if (!(await isImageURL(url, { forceFetch }))) {
if (!(await isImageURL(fetchUrl, { forceFetch }))) {
console.log('[imgproxy] id:', id, '-- not image url:', url)
continue
}
imgproxyUrls[url] = {
dimensions: await getDimensions(url)
dimensions: await getDimensions(fetchUrl)
}
for (const res of resolutions) {
const [w, h] = res.split('x')
const processingOptions = `/rs:fit:${w}:${h}`
imgproxyUrls[url][`${w}w`] = createImgproxyUrl({ url, options: processingOptions })
imgproxyUrls[url][`${w}w`] = createImgproxyPath({ url: fetchUrl, options: processingOptions })
}
}
return imgproxyUrls
@ -115,17 +121,17 @@ export const createImgproxyUrls = async (id, text, { models, forceFetch }) => {
const getDimensions = async (url) => {
const options = '/d:1'
const imgproxyUrl = createImgproxyUrl({ url, options, pathname: 'info' })
const imgproxyUrl = new URL(createImgproxyPath({ url, options, pathname: '/info' }), IMGPROXY_URL).toString()
const res = await fetch(imgproxyUrl)
const { width, height } = await res.json()
return { width, height }
}
const createImgproxyUrl = ({ url, pathname = '', options }) => {
const createImgproxyPath = ({ url, pathname = '/', options }) => {
const b64Url = Buffer.from(url, 'utf-8').toString('base64url')
const target = path.join(options, b64Url)
const signature = sign(target)
return new URL(path.join(pathname, signature, target), IMGPROXY_URL).toString()
return path.join(pathname, signature, target)
}
async function fetchWithTimeout (resource, { timeout = 1000, ...options } = {}) {