1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-10-31 00:07:48 +02:00

Transcribe: Resolves #12874: Downscale images before storing (#13333)

This commit is contained in:
Laurent Cozic
2025-09-30 23:17:01 +01:00
committed by GitHub
parent 15839a19fd
commit d8677a70dd
13 changed files with 656 additions and 33 deletions

View File

@@ -9,6 +9,7 @@ API_KEY=random-string
QUEUE_TTL=900000
QUEUE_RETRY_COUNT=2
QUEUE_MAINTENANCE_INTERVAL=30000
IMAGE_MAX_DIMENSION=400
HTR_CLI_DOCKER_IMAGE=joplin/htr-cli:latest
# Fullpath to images folder e.g.:

View File

@@ -363,7 +363,6 @@
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/React-Core_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact/React-cxxreact_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/boost/boost_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/glog/glog_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/react-native-image-picker/RNImagePickerPrivacyInfo.bundle",
);
name = "[CP] Copy Pods Resources";
@@ -395,7 +394,6 @@
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-Core_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/React-cxxreact_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/boost_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/glog_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNImagePickerPrivacyInfo.bundle",
);
runOnlyForDeploymentPostprocessing = 0;

View File

@@ -1458,7 +1458,7 @@ PODS:
- Yoga
- react-native-get-random-values (1.11.0):
- React-Core
- react-native-image-picker (8.0.0):
- react-native-image-picker (8.2.1):
- DoubleConversion
- glog
- hermes-engine
@@ -1486,7 +1486,7 @@ PODS:
- React-Core
- react-native-netinfo (11.4.1):
- React-Core
- react-native-quick-crypto (0.7.13):
- react-native-quick-crypto (0.7.17):
- DoubleConversion
- glog
- hermes-engine
@@ -1874,7 +1874,7 @@ PODS:
- React-Core
- RNCPushNotificationIOS (1.11.0):
- React-Core
- RNDateTimePicker (8.3.0):
- RNDateTimePicker (8.4.2):
- React-Core
- RNDeviceInfo (14.0.4):
- React-Core
@@ -2285,7 +2285,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5
EXAV: ae28256069c4cdde93d185c007d8f68d92902c2e
EXConstants: 98bcf0f22b820f9b28f9fee55ff2daededadd2f8
Expo: 4b1c6de7c441e1caa1918671ae0aa34d51f019a5
@@ -2298,7 +2298,7 @@ SPEC CHECKSUMS:
fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6
FBLazyVector: 84b955f7b4da8b895faf5946f73748267347c975
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2
hermes-engine: 314be5250afa5692b57b4dd1705959e1973a8ebe
JoplinCommonShareExtension: a8b60b02704d85a7305627912c0240e94af78db7
JoplinRNShareExtension: e158a4b53ee0aa9cd3037a16221dc8adbd6f7860
@@ -2338,10 +2338,10 @@ SPEC CHECKSUMS:
react-native-document-picker: da39c5e4f279d39c0356dca157b98f9dc349e5bb
react-native-geolocation: ec15ffebc53790314885eb9e5f2132132fbc2600
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
react-native-image-picker: 922b9ba90f144b5866d07d04b0fb2b4e9ab0ed75
react-native-image-picker: 7babe45e727db306b3f00d08c72eda3586d6e9c1
react-native-image-resizer: 24c5d06fae2176dc0caed4b6396e02befb44064a
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187
react-native-quick-crypto: 988d8d57cd720dbe218272b60775a8e0210d0b80
react-native-quick-crypto: b475b71e7fa4dbf3446be55e8ad4ef2c58ac4f7f
react-native-rsa-native: a7931cdda1f73a8576a46d7f431378c5550f0c38
react-native-saf-x: 3f8b52fb8160d7322161dec02a564271cc8f4138
react-native-safe-area-context: dde2052b903c11d677c320b599c3244021c34ce8
@@ -2382,7 +2382,7 @@ SPEC CHECKSUMS:
rn-fetch-blob: 25612b6d6f6e980c6f17ed98ba2f58f5696a51ca
RNCClipboard: e1d17c9d093d8129ef50b39b63a17a0e8ccd0ade
RNCPushNotificationIOS: 6c4ca3388c7434e4a662b92e4dfeeee858e6f440
RNDateTimePicker: 29264364ea7b8cc0fb355b3843cf276a4ff78966
RNDateTimePicker: 392bdc0d6863b5de2fe9b957c82c25b6a038db29
RNDeviceInfo: d863506092aef7e7af3a1c350c913d867d795047
RNExitApp: 4432b9b7cc5ccec9f91c94e507849891282befd4
RNFileViewer: 4b5d83358214347e4ab2d4ca8d5c1c90d869e251

View File

@@ -23,6 +23,7 @@
"koa": "2.16.1",
"koa-body": "6.0.1",
"pg-boss": "10.2.0",
"sharp": "0.34.1",
"sqlite3": "5.1.6"
},
"devDependencies": {
@@ -31,6 +32,7 @@
"@types/jest": "29.5.14",
"@types/jest-expect-message": "1.1.0",
"@types/koa": "2.15.0",
"@types/sharp": "0.32.0",
"@types/uuid": "10.0.0",
"gulp": "4.0.2",
"jest": "29.7.0",

View File

@@ -29,53 +29,60 @@ describe('createJob', () => {
});
it('should be able to store a image and retrieve a job', async () => {
const requirements = {
filepath: 'filepath',
storeImage: () => Promise.resolve('file-id'),
sendToQueue: (data: JobData) => queue.send(data),
await copyFile('./images/htr_sample.png', './test_file-1.png');
const fileStorage = new FileStorage();
const requirements = {
filepath: './test_file-1.png',
storeImage: (filePath: string) => fileStorage.store(filePath),
sendToQueue: (data: JobData) => queue.send(data),
imageMaxDimension: 400,
randomName: 'test_file_resized-1',
};
const result = await createJob(requirements);
const job = await queue.fetch();
if (job === null) throw new Error('Should not be null');
expect(result.jobId).toEqual(job.id);
expect(job).toEqual({
data: {
filePath: 'file-id',
},
id: result.jobId,
retryCount: 0,
});
await remove(join('images', job.data.filePath));
});
it('should fail if is not possible to store image', async () => {
await copyFile('./images/htr_sample.png', './test_file-2.png');
const requirements = {
filepath: 'filepath',
filepath: './test_file-2.png',
storeImage: () => { throw new Error('Something went wrong'); },
sendToQueue: (data: JobData) => queue.send(data),
imageMaxDimension: 400,
randomName: 'test_file_resized-2',
};
expect(async () => createJob(requirements)).rejects.toThrow();
const job = await queue.fetch();
expect(job).toBeNull();
await remove(join(process.cwd(), 'images', requirements.randomName));
});
it('should delete the original file after storing', async () => {
await copyFile('./images/htr_sample.png', './test_file.png');
await copyFile('./images/htr_sample.png', './test_file-3.png');
const fs = new FileStorage();
const requirements = {
filepath: './test_file.png',
filepath: './test_file-3.png',
storeImage: fs.store,
sendToQueue: (data: JobData) => queue.send(data),
imageMaxDimension: 400,
randomName: 'test_file_resized-3',
};
await createJob(requirements);
const originalFile = await exists('./test_file.png');
const originalFile = await exists('./test_file-3.png');
expect(originalFile).toBe(false);
});
});

View File

@@ -1,5 +1,7 @@
import Logger from '@joplin/utils/Logger';
import { JobData } from '../../types';
import resizeImageAndDeleteInput from '../utils/resizeImageAndDeleteInput';
import { join } from 'path';
const logger = Logger.create('createJob');
@@ -7,10 +9,16 @@ type CreateJobContext = {
storeImage: (filePath: string)=> Promise<string>;
sendToQueue: (data: JobData)=> Promise<string | null>;
filepath: string;
imageMaxDimension: number;
randomName: string;
};
const createJob = async (context: CreateJobContext) => {
const filePath = await context.storeImage(context.filepath);
const imageResizedPath = join('images', context.randomName);
await resizeImageAndDeleteInput(context.filepath, imageResizedPath, context.imageMaxDimension);
const filePath = await context.storeImage(imageResizedPath);
const jobId = await context.sendToQueue({ filePath });

View File

@@ -1,4 +1,6 @@
import env from '../../env';
import { ErrorBadRequest } from '../../errors';
import createFilename from '../../services/createFilename';
import { AppContext, JobData } from '../../types';
import isFileAValidImage, { supportedImageFormat } from './isFileAValidImage';
@@ -24,6 +26,8 @@ export const parseCreateJobRequest = async (ctx: AppContext) => {
storeImage: (file: string) => ctx.storage.store(file),
sendToQueue: (data: JobData) => ctx.queue.send(data),
filepath: file.filepath,
imageMaxDimension: env().IMAGE_MAX_DIMENSION,
randomName: createFilename(),
};
};

View File

@@ -0,0 +1,76 @@
import { copy, exists, remove } from 'fs-extra';
import resizeImageAndDeleteInput from './resizeImageAndDeleteInput';
import * as sharp from 'sharp';
describe('resizeImage', () => {
it('should resize the image to the max dimension specified', async () => {
const fullFilePath = `${process.cwd()}/test-cases/sample.jpeg`;
const copiedFilePath = `${process.cwd()}/test-cases/sample-copied.jpeg`;
const resizedImageFilePath = `${process.cwd()}/test-cases/sample-copied-resized.jpeg`;
await copy(fullFilePath, copiedFilePath);
await resizeImageAndDeleteInput(copiedFilePath, resizedImageFilePath, 400);
const metadata = await sharp(resizedImageFilePath).metadata();
expect(metadata.width).toBe(400);
expect(metadata.height).toBe(266);
await remove(resizedImageFilePath);
});
it('should keep image aspect ratio', async () => {
const fullFilePath = `${process.cwd()}/test-cases/sample.jpeg`;
const copiedFilePath = `${process.cwd()}/test-cases/sample-copied.jpeg`;
const resizedImageFilePath = `${process.cwd()}/test-cases/sample-copied-resized.jpeg`;
await copy(fullFilePath, copiedFilePath);
const originalMetadata = await sharp(copiedFilePath).metadata();
await resizeImageAndDeleteInput(copiedFilePath, resizedImageFilePath, 400);
const metadata = await sharp(resizedImageFilePath).metadata();
if (originalMetadata.width === undefined || originalMetadata.height === undefined ||
metadata.width === undefined || metadata.height === undefined) {
expect('Image is missing metadata information').toBe(false);
return;
}
expect(originalMetadata.width / originalMetadata.height).toBeCloseTo(metadata.width / metadata.height);
await remove(resizedImageFilePath);
});
it('should remove original image', async () => {
const fullFilePath = `${process.cwd()}/test-cases/sample.jpeg`;
const copiedFilePath = `${process.cwd()}/test-cases/sample-copied.jpeg`;
const resizedImageFilePath = `${process.cwd()}/test-cases/sample-copied-resized.jpeg`;
await copy(fullFilePath, copiedFilePath);
await resizeImageAndDeleteInput(copiedFilePath, resizedImageFilePath, 400);
const doesFileExists = await exists(copiedFilePath);
expect(doesFileExists).toBe(false);
await remove(resizedImageFilePath);
});
it('should return original image if no resize is needed', async () => {
const fullFilePath = `${process.cwd()}/test-cases/sample.jpeg`;
const copiedFilePath = `${process.cwd()}/test-cases/sample-copied.jpeg`;
const resizedImageFilePath = `${process.cwd()}/test-cases/sample-copied-resized.jpeg`;
await copy(fullFilePath, copiedFilePath);
const originalMetadata = await sharp(copiedFilePath).metadata();
await resizeImageAndDeleteInput(copiedFilePath, resizedImageFilePath, 1000);
const resizedImageMetadata = await sharp(resizedImageFilePath).metadata();
expect(originalMetadata.width).toBe(resizedImageMetadata.width);
expect(originalMetadata.height).toBe(resizedImageMetadata.height);
expect(originalMetadata.size).toBe(resizedImageMetadata.size);
await remove(resizedImageFilePath);
});
});

View File

@@ -0,0 +1,30 @@
import { remove, copy } from 'fs-extra';
import * as sharp from 'sharp';
// Strange function that deletes the input file so we give it a specific name, because it shouldn't
// be used as a general function to resize an image. Should probably be refactored but for now it's
// good enough.
const resizeImageAndDeleteInput = async (inputPath: string, outputPath: string, imageMaxDimension: number) => {
const metadata = await sharp(inputPath).metadata();
if (!metadata || metadata.width === undefined || metadata.height === undefined) {
await copy(inputPath, outputPath);
await remove(inputPath);
return;
}
if (Math.max(metadata?.width, metadata?.height) <= imageMaxDimension) {
await copy(inputPath, outputPath);
await remove(inputPath);
return;
}
await sharp(inputPath)
.resize(imageMaxDimension, imageMaxDimension, { fit: 'inside', withoutEnlargement: true })
.toFile(outputPath);
await remove(inputPath);
};
export default resizeImageAndDeleteInput;

View File

@@ -16,6 +16,7 @@ export const defaultEnvValues: EnvVariables = {
FILE_STORAGE_MAINTENANCE_INTERVAL: 1 * Hour,
FILE_STORAGE_TTL: 7 * Day,
QUEUE_DATABASE_HOST: 'localhost',
IMAGE_MAX_DIMENSION: 400,
};
export interface EnvVariables {
@@ -34,6 +35,7 @@ export interface EnvVariables {
FILE_STORAGE_MAINTENANCE_INTERVAL: number;
FILE_STORAGE_TTL: number;
QUEUE_DATABASE_HOST: string;
IMAGE_MAX_DIMENSION: number;
}
export function parseEnv(rawEnv: Record<string, string | undefined>): EnvVariables {

View File

@@ -1,8 +1,8 @@
import { join } from 'path';
import { move, readdir, remove } from 'fs-extra';
import { randomBytes } from 'crypto';
import { ContentStorage } from '../types';
import Logger from '@joplin/utils/Logger';
import createFilename from './createFilename';
const logger = Logger.create('FileStorage');
@@ -13,10 +13,8 @@ export default class FileStorage implements ContentStorage {
private isMaintenanceRunning = false;
public async store(filepath: string) {
const time = new Date().getTime();
const random = randomBytes(16).toString('hex');
const randomName = `${time}_${random}`;
await move(filepath, join(imagesFolderPath, randomName));
const randomName = createFilename();
await move(filepath, join('images', randomName));
return randomName;
}

View File

@@ -0,0 +1,9 @@
import { randomBytes } from 'crypto';
const createFilename = () => {
const time = new Date().getTime();
const random = randomBytes(16).toString('hex');
return `${time}_${random}`;
};
export default createFilename;

490
yarn.lock
View File

@@ -7273,7 +7273,7 @@ __metadata:
languageName: node
linkType: hard
"@emnapi/runtime@npm:^1.4.3":
"@emnapi/runtime@npm:^1.4.0, @emnapi/runtime@npm:^1.4.3, @emnapi/runtime@npm:^1.4.4":
version: 1.4.5
resolution: "@emnapi/runtime@npm:1.4.5"
dependencies:
@@ -8501,6 +8501,18 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-darwin-arm64@npm:0.34.1":
version: 0.34.1
resolution: "@img/sharp-darwin-arm64@npm:0.34.1"
dependencies:
"@img/sharp-libvips-darwin-arm64": "npm:1.1.0"
dependenciesMeta:
"@img/sharp-libvips-darwin-arm64":
optional: true
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@img/sharp-darwin-arm64@npm:0.34.2":
version: 0.34.2
resolution: "@img/sharp-darwin-arm64@npm:0.34.2"
@@ -8513,6 +8525,30 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-darwin-arm64@npm:0.34.3":
version: 0.34.3
resolution: "@img/sharp-darwin-arm64@npm:0.34.3"
dependencies:
"@img/sharp-libvips-darwin-arm64": "npm:1.2.0"
dependenciesMeta:
"@img/sharp-libvips-darwin-arm64":
optional: true
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@img/sharp-darwin-x64@npm:0.34.1":
version: 0.34.1
resolution: "@img/sharp-darwin-x64@npm:0.34.1"
dependencies:
"@img/sharp-libvips-darwin-x64": "npm:1.1.0"
dependenciesMeta:
"@img/sharp-libvips-darwin-x64":
optional: true
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@img/sharp-darwin-x64@npm:0.34.2":
version: 0.34.2
resolution: "@img/sharp-darwin-x64@npm:0.34.2"
@@ -8525,6 +8561,18 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-darwin-x64@npm:0.34.3":
version: 0.34.3
resolution: "@img/sharp-darwin-x64@npm:0.34.3"
dependencies:
"@img/sharp-libvips-darwin-x64": "npm:1.2.0"
dependenciesMeta:
"@img/sharp-libvips-darwin-x64":
optional: true
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@img/sharp-libvips-darwin-arm64@npm:1.1.0":
version: 1.1.0
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.1.0"
@@ -8532,6 +8580,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-darwin-arm64@npm:1.2.0":
version: 1.2.0
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.0"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@img/sharp-libvips-darwin-x64@npm:1.1.0":
version: 1.1.0
resolution: "@img/sharp-libvips-darwin-x64@npm:1.1.0"
@@ -8539,6 +8594,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-darwin-x64@npm:1.2.0":
version: 1.2.0
resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.0"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@img/sharp-libvips-linux-arm64@npm:1.1.0":
version: 1.1.0
resolution: "@img/sharp-libvips-linux-arm64@npm:1.1.0"
@@ -8546,6 +8608,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linux-arm64@npm:1.2.0":
version: 1.2.0
resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.0"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-libvips-linux-arm@npm:1.1.0":
version: 1.1.0
resolution: "@img/sharp-libvips-linux-arm@npm:1.1.0"
@@ -8553,6 +8622,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linux-arm@npm:1.2.0":
version: 1.2.0
resolution: "@img/sharp-libvips-linux-arm@npm:1.2.0"
conditions: os=linux & cpu=arm & libc=glibc
languageName: node
linkType: hard
"@img/sharp-libvips-linux-ppc64@npm:1.1.0":
version: 1.1.0
resolution: "@img/sharp-libvips-linux-ppc64@npm:1.1.0"
@@ -8560,6 +8636,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linux-ppc64@npm:1.2.0":
version: 1.2.0
resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.0"
conditions: os=linux & cpu=ppc64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-libvips-linux-s390x@npm:1.1.0":
version: 1.1.0
resolution: "@img/sharp-libvips-linux-s390x@npm:1.1.0"
@@ -8567,6 +8650,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linux-s390x@npm:1.2.0":
version: 1.2.0
resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.0"
conditions: os=linux & cpu=s390x & libc=glibc
languageName: node
linkType: hard
"@img/sharp-libvips-linux-x64@npm:1.1.0":
version: 1.1.0
resolution: "@img/sharp-libvips-linux-x64@npm:1.1.0"
@@ -8574,6 +8664,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linux-x64@npm:1.2.0":
version: 1.2.0
resolution: "@img/sharp-libvips-linux-x64@npm:1.2.0"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-libvips-linuxmusl-arm64@npm:1.1.0":
version: 1.1.0
resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.1.0"
@@ -8581,6 +8678,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.0":
version: 1.2.0
resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.0"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@img/sharp-libvips-linuxmusl-x64@npm:1.1.0":
version: 1.1.0
resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.1.0"
@@ -8588,6 +8692,25 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linuxmusl-x64@npm:1.2.0":
version: 1.2.0
resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.0"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@img/sharp-linux-arm64@npm:0.34.1":
version: 0.34.1
resolution: "@img/sharp-linux-arm64@npm:0.34.1"
dependencies:
"@img/sharp-libvips-linux-arm64": "npm:1.1.0"
dependenciesMeta:
"@img/sharp-libvips-linux-arm64":
optional: true
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-arm64@npm:0.34.2":
version: 0.34.2
resolution: "@img/sharp-linux-arm64@npm:0.34.2"
@@ -8600,6 +8723,30 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linux-arm64@npm:0.34.3":
version: 0.34.3
resolution: "@img/sharp-linux-arm64@npm:0.34.3"
dependencies:
"@img/sharp-libvips-linux-arm64": "npm:1.2.0"
dependenciesMeta:
"@img/sharp-libvips-linux-arm64":
optional: true
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-arm@npm:0.34.1":
version: 0.34.1
resolution: "@img/sharp-linux-arm@npm:0.34.1"
dependencies:
"@img/sharp-libvips-linux-arm": "npm:1.1.0"
dependenciesMeta:
"@img/sharp-libvips-linux-arm":
optional: true
conditions: os=linux & cpu=arm & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-arm@npm:0.34.2":
version: 0.34.2
resolution: "@img/sharp-linux-arm@npm:0.34.2"
@@ -8612,6 +8759,42 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linux-arm@npm:0.34.3":
version: 0.34.3
resolution: "@img/sharp-linux-arm@npm:0.34.3"
dependencies:
"@img/sharp-libvips-linux-arm": "npm:1.2.0"
dependenciesMeta:
"@img/sharp-libvips-linux-arm":
optional: true
conditions: os=linux & cpu=arm & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-ppc64@npm:0.34.3":
version: 0.34.3
resolution: "@img/sharp-linux-ppc64@npm:0.34.3"
dependencies:
"@img/sharp-libvips-linux-ppc64": "npm:1.2.0"
dependenciesMeta:
"@img/sharp-libvips-linux-ppc64":
optional: true
conditions: os=linux & cpu=ppc64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-s390x@npm:0.34.1":
version: 0.34.1
resolution: "@img/sharp-linux-s390x@npm:0.34.1"
dependencies:
"@img/sharp-libvips-linux-s390x": "npm:1.1.0"
dependenciesMeta:
"@img/sharp-libvips-linux-s390x":
optional: true
conditions: os=linux & cpu=s390x & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-s390x@npm:0.34.2":
version: 0.34.2
resolution: "@img/sharp-linux-s390x@npm:0.34.2"
@@ -8624,6 +8807,30 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linux-s390x@npm:0.34.3":
version: 0.34.3
resolution: "@img/sharp-linux-s390x@npm:0.34.3"
dependencies:
"@img/sharp-libvips-linux-s390x": "npm:1.2.0"
dependenciesMeta:
"@img/sharp-libvips-linux-s390x":
optional: true
conditions: os=linux & cpu=s390x & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-x64@npm:0.34.1":
version: 0.34.1
resolution: "@img/sharp-linux-x64@npm:0.34.1"
dependencies:
"@img/sharp-libvips-linux-x64": "npm:1.1.0"
dependenciesMeta:
"@img/sharp-libvips-linux-x64":
optional: true
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-x64@npm:0.34.2":
version: 0.34.2
resolution: "@img/sharp-linux-x64@npm:0.34.2"
@@ -8636,6 +8843,30 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linux-x64@npm:0.34.3":
version: 0.34.3
resolution: "@img/sharp-linux-x64@npm:0.34.3"
dependencies:
"@img/sharp-libvips-linux-x64": "npm:1.2.0"
dependenciesMeta:
"@img/sharp-libvips-linux-x64":
optional: true
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linuxmusl-arm64@npm:0.34.1":
version: 0.34.1
resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.1"
dependencies:
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.1.0"
dependenciesMeta:
"@img/sharp-libvips-linuxmusl-arm64":
optional: true
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@img/sharp-linuxmusl-arm64@npm:0.34.2":
version: 0.34.2
resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.2"
@@ -8648,6 +8879,30 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linuxmusl-arm64@npm:0.34.3":
version: 0.34.3
resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.3"
dependencies:
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.0"
dependenciesMeta:
"@img/sharp-libvips-linuxmusl-arm64":
optional: true
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@img/sharp-linuxmusl-x64@npm:0.34.1":
version: 0.34.1
resolution: "@img/sharp-linuxmusl-x64@npm:0.34.1"
dependencies:
"@img/sharp-libvips-linuxmusl-x64": "npm:1.1.0"
dependenciesMeta:
"@img/sharp-libvips-linuxmusl-x64":
optional: true
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@img/sharp-linuxmusl-x64@npm:0.34.2":
version: 0.34.2
resolution: "@img/sharp-linuxmusl-x64@npm:0.34.2"
@@ -8660,6 +8915,27 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linuxmusl-x64@npm:0.34.3":
version: 0.34.3
resolution: "@img/sharp-linuxmusl-x64@npm:0.34.3"
dependencies:
"@img/sharp-libvips-linuxmusl-x64": "npm:1.2.0"
dependenciesMeta:
"@img/sharp-libvips-linuxmusl-x64":
optional: true
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@img/sharp-wasm32@npm:0.34.1":
version: 0.34.1
resolution: "@img/sharp-wasm32@npm:0.34.1"
dependencies:
"@emnapi/runtime": "npm:^1.4.0"
conditions: cpu=wasm32
languageName: node
linkType: hard
"@img/sharp-wasm32@npm:0.34.2":
version: 0.34.2
resolution: "@img/sharp-wasm32@npm:0.34.2"
@@ -8669,6 +8945,15 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-wasm32@npm:0.34.3":
version: 0.34.3
resolution: "@img/sharp-wasm32@npm:0.34.3"
dependencies:
"@emnapi/runtime": "npm:^1.4.4"
conditions: cpu=wasm32
languageName: node
linkType: hard
"@img/sharp-win32-arm64@npm:0.34.2":
version: 0.34.2
resolution: "@img/sharp-win32-arm64@npm:0.34.2"
@@ -8676,6 +8961,20 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-win32-arm64@npm:0.34.3":
version: 0.34.3
resolution: "@img/sharp-win32-arm64@npm:0.34.3"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@img/sharp-win32-ia32@npm:0.34.1":
version: 0.34.1
resolution: "@img/sharp-win32-ia32@npm:0.34.1"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@img/sharp-win32-ia32@npm:0.34.2":
version: 0.34.2
resolution: "@img/sharp-win32-ia32@npm:0.34.2"
@@ -8683,6 +8982,20 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-win32-ia32@npm:0.34.3":
version: 0.34.3
resolution: "@img/sharp-win32-ia32@npm:0.34.3"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@img/sharp-win32-x64@npm:0.34.1":
version: 0.34.1
resolution: "@img/sharp-win32-x64@npm:0.34.1"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@img/sharp-win32-x64@npm:0.34.2":
version: 0.34.2
resolution: "@img/sharp-win32-x64@npm:0.34.2"
@@ -8690,6 +9003,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-win32-x64@npm:0.34.3":
version: 0.34.3
resolution: "@img/sharp-win32-x64@npm:0.34.3"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@isaacs/balanced-match@npm:^4.0.1":
version: 4.0.1
resolution: "@isaacs/balanced-match@npm:4.0.1"
@@ -9825,6 +10145,7 @@ __metadata:
"@types/jest": "npm:29.5.14"
"@types/jest-expect-message": "npm:1.1.0"
"@types/koa": "npm:2.15.0"
"@types/sharp": "npm:0.32.0"
"@types/uuid": "npm:10.0.0"
dotenv: "npm:16.5.0"
file-type: "npm:16.5.4"
@@ -9836,6 +10157,7 @@ __metadata:
koa: "npm:2.16.1"
koa-body: "npm:6.0.1"
pg-boss: "npm:10.2.0"
sharp: "npm:0.34.1"
sqlite3: "npm:5.1.6"
typescript: "npm:5.8.2"
languageName: unknown
@@ -14836,6 +15158,15 @@ __metadata:
languageName: node
linkType: hard
"@types/sharp@npm:0.32.0":
version: 0.32.0
resolution: "@types/sharp@npm:0.32.0"
dependencies:
sharp: "npm:*"
checksum: 10/9d0ca925ebb18e870740db0e0abcaaa2458645673068166b9f26e042640fa430644fe39aada320e03834f7ba3600e678de822201ffc312f9df1f515ba107ebd5
languageName: node
linkType: hard
"@types/sockjs@npm:^0.3.33":
version: 0.3.35
resolution: "@types/sockjs@npm:0.3.35"
@@ -23264,6 +23595,13 @@ __metadata:
languageName: node
linkType: hard
"detect-libc@npm:^2.0.3":
version: 2.0.3
resolution: "detect-libc@npm:2.0.3"
checksum: 10/b4ea018d623e077bd395f168a9e81db77370dde36a5b01d067f2ad7989924a81d31cb547ff764acb2aa25d50bb7fdde0b0a93bec02212b0cb430621623246d39
languageName: node
linkType: hard
"detect-libc@npm:^2.0.4":
version: 2.0.4
resolution: "detect-libc@npm:2.0.4"
@@ -44802,6 +45140,156 @@ __metadata:
languageName: node
linkType: hard
"sharp@npm:*":
version: 0.34.3
resolution: "sharp@npm:0.34.3"
dependencies:
"@img/sharp-darwin-arm64": "npm:0.34.3"
"@img/sharp-darwin-x64": "npm:0.34.3"
"@img/sharp-libvips-darwin-arm64": "npm:1.2.0"
"@img/sharp-libvips-darwin-x64": "npm:1.2.0"
"@img/sharp-libvips-linux-arm": "npm:1.2.0"
"@img/sharp-libvips-linux-arm64": "npm:1.2.0"
"@img/sharp-libvips-linux-ppc64": "npm:1.2.0"
"@img/sharp-libvips-linux-s390x": "npm:1.2.0"
"@img/sharp-libvips-linux-x64": "npm:1.2.0"
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.0"
"@img/sharp-libvips-linuxmusl-x64": "npm:1.2.0"
"@img/sharp-linux-arm": "npm:0.34.3"
"@img/sharp-linux-arm64": "npm:0.34.3"
"@img/sharp-linux-ppc64": "npm:0.34.3"
"@img/sharp-linux-s390x": "npm:0.34.3"
"@img/sharp-linux-x64": "npm:0.34.3"
"@img/sharp-linuxmusl-arm64": "npm:0.34.3"
"@img/sharp-linuxmusl-x64": "npm:0.34.3"
"@img/sharp-wasm32": "npm:0.34.3"
"@img/sharp-win32-arm64": "npm:0.34.3"
"@img/sharp-win32-ia32": "npm:0.34.3"
"@img/sharp-win32-x64": "npm:0.34.3"
color: "npm:^4.2.3"
detect-libc: "npm:^2.0.4"
semver: "npm:^7.7.2"
dependenciesMeta:
"@img/sharp-darwin-arm64":
optional: true
"@img/sharp-darwin-x64":
optional: true
"@img/sharp-libvips-darwin-arm64":
optional: true
"@img/sharp-libvips-darwin-x64":
optional: true
"@img/sharp-libvips-linux-arm":
optional: true
"@img/sharp-libvips-linux-arm64":
optional: true
"@img/sharp-libvips-linux-ppc64":
optional: true
"@img/sharp-libvips-linux-s390x":
optional: true
"@img/sharp-libvips-linux-x64":
optional: true
"@img/sharp-libvips-linuxmusl-arm64":
optional: true
"@img/sharp-libvips-linuxmusl-x64":
optional: true
"@img/sharp-linux-arm":
optional: true
"@img/sharp-linux-arm64":
optional: true
"@img/sharp-linux-ppc64":
optional: true
"@img/sharp-linux-s390x":
optional: true
"@img/sharp-linux-x64":
optional: true
"@img/sharp-linuxmusl-arm64":
optional: true
"@img/sharp-linuxmusl-x64":
optional: true
"@img/sharp-wasm32":
optional: true
"@img/sharp-win32-arm64":
optional: true
"@img/sharp-win32-ia32":
optional: true
"@img/sharp-win32-x64":
optional: true
checksum: 10/b8ca871c99b48601c47f5dfabf32e38e60071a93e359b3c765d398f708a7cf3735d1bd804b72a957246a3b215fd281a17f887d9c36ebfa690c90fa5fe142d2cd
languageName: node
linkType: hard
"sharp@npm:0.34.1":
version: 0.34.1
resolution: "sharp@npm:0.34.1"
dependencies:
"@img/sharp-darwin-arm64": "npm:0.34.1"
"@img/sharp-darwin-x64": "npm:0.34.1"
"@img/sharp-libvips-darwin-arm64": "npm:1.1.0"
"@img/sharp-libvips-darwin-x64": "npm:1.1.0"
"@img/sharp-libvips-linux-arm": "npm:1.1.0"
"@img/sharp-libvips-linux-arm64": "npm:1.1.0"
"@img/sharp-libvips-linux-ppc64": "npm:1.1.0"
"@img/sharp-libvips-linux-s390x": "npm:1.1.0"
"@img/sharp-libvips-linux-x64": "npm:1.1.0"
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.1.0"
"@img/sharp-libvips-linuxmusl-x64": "npm:1.1.0"
"@img/sharp-linux-arm": "npm:0.34.1"
"@img/sharp-linux-arm64": "npm:0.34.1"
"@img/sharp-linux-s390x": "npm:0.34.1"
"@img/sharp-linux-x64": "npm:0.34.1"
"@img/sharp-linuxmusl-arm64": "npm:0.34.1"
"@img/sharp-linuxmusl-x64": "npm:0.34.1"
"@img/sharp-wasm32": "npm:0.34.1"
"@img/sharp-win32-ia32": "npm:0.34.1"
"@img/sharp-win32-x64": "npm:0.34.1"
color: "npm:^4.2.3"
detect-libc: "npm:^2.0.3"
semver: "npm:^7.7.1"
dependenciesMeta:
"@img/sharp-darwin-arm64":
optional: true
"@img/sharp-darwin-x64":
optional: true
"@img/sharp-libvips-darwin-arm64":
optional: true
"@img/sharp-libvips-darwin-x64":
optional: true
"@img/sharp-libvips-linux-arm":
optional: true
"@img/sharp-libvips-linux-arm64":
optional: true
"@img/sharp-libvips-linux-ppc64":
optional: true
"@img/sharp-libvips-linux-s390x":
optional: true
"@img/sharp-libvips-linux-x64":
optional: true
"@img/sharp-libvips-linuxmusl-arm64":
optional: true
"@img/sharp-libvips-linuxmusl-x64":
optional: true
"@img/sharp-linux-arm":
optional: true
"@img/sharp-linux-arm64":
optional: true
"@img/sharp-linux-s390x":
optional: true
"@img/sharp-linux-x64":
optional: true
"@img/sharp-linuxmusl-arm64":
optional: true
"@img/sharp-linuxmusl-x64":
optional: true
"@img/sharp-wasm32":
optional: true
"@img/sharp-win32-ia32":
optional: true
"@img/sharp-win32-x64":
optional: true
checksum: 10/aecb960c0780b56134bfef01b7aeaa4e6650320a8a1f491237b45e900fc670830ee5d0600f30e51878328109db82e376bb526931d07a2e9358510ef30ab5abe8
languageName: node
linkType: hard
"sharp@npm:0.34.2":
version: 0.34.2
resolution: "sharp@npm:0.34.2"