diff --git a/src/config.ts b/src/config.ts index c4d6a9b..c259563 100644 --- a/src/config.ts +++ b/src/config.ts @@ -139,6 +139,10 @@ class Conf { static get uploader() { return Deno.env.get('DITTO_UPLOADER'); } + /** Location to use for local uploads. */ + static get uploadsDir() { + return Deno.env.get('UPLOADS_DIR') || 'data/uploads'; + } /** Media base URL for uploads. */ static get mediaDomain() { const value = Deno.env.get('MEDIA_DOMAIN'); diff --git a/src/uploaders/config.ts b/src/uploaders/config.ts index 83874f6..5b4c7af 100644 --- a/src/uploaders/config.ts +++ b/src/uploaders/config.ts @@ -1,7 +1,8 @@ import { Conf } from '@/config.ts'; -import { ipfsUploader } from './ipfs.ts'; -import { s3Uploader } from './s3.ts'; +import { ipfsUploader } from '@/uploaders/ipfs.ts'; +import { localUploader } from '@/uploaders/local.ts'; +import { s3Uploader } from '@/uploaders/s3.ts'; import type { Uploader } from './types.ts'; @@ -10,8 +11,8 @@ const configUploader: Uploader = { upload(file, opts) { return uploader().upload(file, opts); }, - delete(cid, opts) { - return uploader().delete(cid, opts); + delete(id, opts) { + return uploader().delete(id, opts); }, }; @@ -22,8 +23,10 @@ function uploader() { return s3Uploader; case 'ipfs': return ipfsUploader; + case 'local': + return localUploader; default: - return ipfsUploader; + throw new Error('No `DITTO_UPLOADER` configured. Uploads are disabled.'); } } diff --git a/src/uploaders/local.ts b/src/uploaders/local.ts new file mode 100644 index 0000000..a2381a3 --- /dev/null +++ b/src/uploaders/local.ts @@ -0,0 +1,36 @@ +import { join } from 'node:path'; + +import { crypto } from '@std/crypto'; +import { encodeHex } from '@std/encoding/hex'; +import { extensionsByType } from '@std/media-types'; + +import { Conf } from '@/config.ts'; + +import type { Uploader } from './types.ts'; + +/** Local filesystem uploader. */ +const localUploader: Uploader = { + async upload(file) { + const sha256 = encodeHex(await crypto.subtle.digest('SHA-256', file.stream())); + const ext = extensionsByType(file.type)?.[0] ?? 'bin'; + const filename = `${sha256}.${ext}`; + + await Deno.mkdir(Conf.uploadsDir, { recursive: true }); + await Deno.writeFile(join(Conf.uploadsDir, filename), file.stream()); + + const { mediaDomain } = Conf; + const url = new URL(mediaDomain); + const path = url.pathname === '/' ? filename : join(url.pathname, filename); + + return { + id: filename, + sha256, + url: new URL(path, url).toString(), + }; + }, + async delete(id) { + await Deno.remove(join(Conf.uploadsDir, id)); + }, +}; + +export { localUploader };