mirror of
https://github.com/papra-hq/papra.git
synced 2025-12-21 12:09:39 -06:00
feat(server): add support for b2 object storage type (#232)
* feat(b2): add support for b2 object storage type * feat(b2): fix order of tsconfig entries * feat(b2): fix accidental responseType change * fix(b2): remove unnecessary try-catches * refactor(b2): use error factories
This commit is contained in:
@@ -41,6 +41,7 @@
|
||||
"@owlrelay/webhook": "^0.0.3",
|
||||
"@papra/lecture": "^0.0.4",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"backblaze-b2": "^1.7.0",
|
||||
"better-auth": "catalog:",
|
||||
"c12": "^3.0.2",
|
||||
"chokidar": "^4.0.3",
|
||||
@@ -66,6 +67,7 @@
|
||||
"@antfu/eslint-config": "catalog:",
|
||||
"@crowlog/pretty": "^1.1.1",
|
||||
"@total-typescript/ts-reset": "^0.6.1",
|
||||
"@types/backblaze-b2": "^1.5.6",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"@types/node": "^22.10.2",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ConfigDefinition } from 'figue';
|
||||
import { z } from 'zod';
|
||||
import { B2_STORAGE_DRIVER_NAME } from './drivers/b2/b2.storage-driver';
|
||||
import { FS_STORAGE_DRIVER_NAME } from './drivers/fs/fs.storage-driver';
|
||||
import { IN_MEMORY_STORAGE_DRIVER_NAME } from './drivers/memory/memory.storage-driver';
|
||||
import { S3_STORAGE_DRIVER_NAME } from './drivers/s3/s3.storage-driver';
|
||||
@@ -12,8 +13,8 @@ export const documentStorageConfig = {
|
||||
env: 'DOCUMENT_STORAGE_MAX_UPLOAD_SIZE',
|
||||
},
|
||||
driver: {
|
||||
doc: `The driver to use for document storage, values can be one of: ${[FS_STORAGE_DRIVER_NAME, S3_STORAGE_DRIVER_NAME, IN_MEMORY_STORAGE_DRIVER_NAME].map(x => `\`${x}\``).join(', ')}`,
|
||||
schema: z.enum([FS_STORAGE_DRIVER_NAME, S3_STORAGE_DRIVER_NAME, IN_MEMORY_STORAGE_DRIVER_NAME]),
|
||||
doc: `The driver to use for document storage, values can be one of: ${[FS_STORAGE_DRIVER_NAME, S3_STORAGE_DRIVER_NAME, IN_MEMORY_STORAGE_DRIVER_NAME, B2_STORAGE_DRIVER_NAME].map(x => `\`${x}\``).join(', ')}`,
|
||||
schema: z.enum([FS_STORAGE_DRIVER_NAME, S3_STORAGE_DRIVER_NAME, IN_MEMORY_STORAGE_DRIVER_NAME, B2_STORAGE_DRIVER_NAME]),
|
||||
default: FS_STORAGE_DRIVER_NAME,
|
||||
env: 'DOCUMENT_STORAGE_DRIVER',
|
||||
},
|
||||
@@ -58,5 +59,31 @@ export const documentStorageConfig = {
|
||||
env: 'DOCUMENT_STORAGE_S3_ENDPOINT',
|
||||
},
|
||||
},
|
||||
b2: {
|
||||
applicationKeyId: {
|
||||
doc: 'The B2 application key ID',
|
||||
schema: z.string(),
|
||||
default: '',
|
||||
env: 'DOCUMENT_STORAGE_B2_APPLICATION_KEY_ID',
|
||||
},
|
||||
applicationKey: {
|
||||
doc: 'The B2 application key',
|
||||
schema: z.string(),
|
||||
default: '',
|
||||
env: 'DOCUMENT_STORAGE_B2_APPLICATION_KEY',
|
||||
},
|
||||
bucketName: {
|
||||
doc: 'The B2 bucket name',
|
||||
schema: z.string(),
|
||||
default: '',
|
||||
env: 'DOCUMENT_STORAGE_B2_BUCKET_NAME',
|
||||
},
|
||||
bucketId: {
|
||||
doc: 'The B2 bucket ID',
|
||||
schema: z.string(),
|
||||
default: '',
|
||||
env: 'DOCUMENT_STORAGE_B2_BUCKET_ID',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const satisfies ConfigDefinition;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Config } from '../../config/config.types';
|
||||
import { createError } from '../../shared/errors/errors';
|
||||
import { B2_STORAGE_DRIVER_NAME, b2StorageDriverFactory } from './drivers/b2/b2.storage-driver';
|
||||
import { FS_STORAGE_DRIVER_NAME, fsStorageDriverFactory } from './drivers/fs/fs.storage-driver';
|
||||
import { IN_MEMORY_STORAGE_DRIVER_NAME, inMemoryStorageDriverFactory } from './drivers/memory/memory.storage-driver';
|
||||
import { S3_STORAGE_DRIVER_NAME, s3StorageDriverFactory } from './drivers/s3/s3.storage-driver';
|
||||
@@ -8,6 +9,7 @@ const storageDriverFactories = {
|
||||
[FS_STORAGE_DRIVER_NAME]: fsStorageDriverFactory,
|
||||
[S3_STORAGE_DRIVER_NAME]: s3StorageDriverFactory,
|
||||
[IN_MEMORY_STORAGE_DRIVER_NAME]: inMemoryStorageDriverFactory,
|
||||
[B2_STORAGE_DRIVER_NAME]: b2StorageDriverFactory,
|
||||
};
|
||||
|
||||
export type DocumentStorageService = Awaited<ReturnType<typeof createDocumentStorageService>>;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Buffer } from 'node:buffer';
|
||||
import B2 from 'backblaze-b2';
|
||||
|
||||
import { createFileNotFoundError } from '../../document-storage.errors';
|
||||
import { defineStorageDriver } from '../drivers.models';
|
||||
|
||||
export const B2_STORAGE_DRIVER_NAME = 'b2' as const;
|
||||
|
||||
export const b2StorageDriverFactory = defineStorageDriver(async ({ config }) => {
|
||||
const { applicationKeyId, applicationKey, bucketId, bucketName } = config.documentsStorage.drivers.b2;
|
||||
|
||||
const b2Client = new B2({
|
||||
applicationKey,
|
||||
applicationKeyId,
|
||||
});
|
||||
|
||||
return {
|
||||
name: B2_STORAGE_DRIVER_NAME,
|
||||
saveFile: async ({ file, storageKey }) => {
|
||||
await b2Client.authorize();
|
||||
const getUploadUrl = await b2Client.getUploadUrl({
|
||||
bucketId,
|
||||
});
|
||||
const upload = await b2Client.uploadFile({
|
||||
uploadUrl: getUploadUrl.data.uploadUrl,
|
||||
uploadAuthToken: getUploadUrl.data.authorizationToken,
|
||||
fileName: storageKey,
|
||||
data: Buffer.from(await file.arrayBuffer()),
|
||||
});
|
||||
if (upload.status !== 200) {
|
||||
throw createFileNotFoundError();
|
||||
}
|
||||
return { storageKey };
|
||||
},
|
||||
getFileStream: async ({ storageKey }) => {
|
||||
await b2Client.authorize();
|
||||
const response = await b2Client.downloadFileByName({
|
||||
bucketName,
|
||||
fileName: storageKey,
|
||||
responseType: 'stream',
|
||||
});
|
||||
if (!response.data) {
|
||||
throw createFileNotFoundError();
|
||||
}
|
||||
return { fileStream: response.data };
|
||||
},
|
||||
deleteFile: async ({ storageKey }) => {
|
||||
await b2Client.hideFile({
|
||||
bucketId,
|
||||
fileName: storageKey,
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -9,6 +9,7 @@
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
|
||||
48
pnpm-lock.yaml
generated
48
pnpm-lock.yaml
generated
@@ -250,6 +250,9 @@ importers:
|
||||
'@paralleldrive/cuid2':
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
backblaze-b2:
|
||||
specifier: ^1.7.0
|
||||
version: 1.7.0
|
||||
better-auth:
|
||||
specifier: 'catalog:'
|
||||
version: 1.2.4(typescript@5.8.2)
|
||||
@@ -320,6 +323,9 @@ importers:
|
||||
'@total-typescript/ts-reset':
|
||||
specifier: ^0.6.1
|
||||
version: 0.6.1
|
||||
'@types/backblaze-b2':
|
||||
specifier: ^1.5.6
|
||||
version: 1.5.6
|
||||
'@types/lodash-es':
|
||||
specifier: ^4.17.12
|
||||
version: 4.17.12
|
||||
@@ -2949,6 +2955,9 @@ packages:
|
||||
'@types/babel__traverse@7.20.6':
|
||||
resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==}
|
||||
|
||||
'@types/backblaze-b2@1.5.6':
|
||||
resolution: {integrity: sha512-IGx7YhySgHYLns8nkDGPcejPpoG20eHdLBjtJK+QIh44OvG/QOBfCivfRajVoYKf2tczqCY/KIdOin6BfkF18Q==}
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||
|
||||
@@ -3578,6 +3587,12 @@ packages:
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
axios-retry@3.9.1:
|
||||
resolution: {integrity: sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==}
|
||||
|
||||
axios@0.21.4:
|
||||
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
|
||||
|
||||
axios@1.8.4:
|
||||
resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==}
|
||||
|
||||
@@ -3598,6 +3613,10 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0
|
||||
|
||||
backblaze-b2@1.7.0:
|
||||
resolution: {integrity: sha512-8cVsKkXspuM1UeLI8WWSWw2JHfB7/IvqTtzvwhHqqhNyqcYl8iZ2lFpeuXGKcFA1TiSRlgALXWFJ9eKG6+3ZPg==}
|
||||
engines: {node: '>=10.0'}
|
||||
|
||||
bail@2.0.2:
|
||||
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
|
||||
|
||||
@@ -5165,6 +5184,10 @@ packages:
|
||||
is-potential-custom-element-name@1.0.1:
|
||||
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
|
||||
|
||||
is-retry-allowed@2.2.0:
|
||||
resolution: {integrity: sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
is-url@1.2.4:
|
||||
resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==}
|
||||
|
||||
@@ -10192,6 +10215,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.26.3
|
||||
|
||||
'@types/backblaze-b2@1.5.6':
|
||||
dependencies:
|
||||
'@types/node': 22.13.10
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
dependencies:
|
||||
'@types/ms': 0.7.34
|
||||
@@ -11090,6 +11117,17 @@ snapshots:
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
axios-retry@3.9.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.0
|
||||
is-retry-allowed: 2.2.0
|
||||
|
||||
axios@0.21.4:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.9
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
axios@1.8.4:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.9
|
||||
@@ -11117,6 +11155,14 @@ snapshots:
|
||||
'@babel/core': 7.26.0
|
||||
babel-plugin-jsx-dom-expressions: 0.39.3(@babel/core@7.26.0)
|
||||
|
||||
backblaze-b2@1.7.0:
|
||||
dependencies:
|
||||
axios: 0.21.4
|
||||
axios-retry: 3.9.1
|
||||
lodash: 4.17.21
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
bail@2.0.2: {}
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
@@ -13161,6 +13207,8 @@ snapshots:
|
||||
|
||||
is-potential-custom-element-name@1.0.1: {}
|
||||
|
||||
is-retry-allowed@2.2.0: {}
|
||||
|
||||
is-url@1.2.4: {}
|
||||
|
||||
is-what@4.1.16: {}
|
||||
|
||||
Reference in New Issue
Block a user