Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@
"@sendgrid/mail": "^8.1.3",
"@types/express-session": "^1.18.1",
"@types/swagger-ui-express": "^4.1.8",
"@aws-sdk/client-s3": "^3.1048.0",
"@aws-sdk/s3-request-presigner": "^3.1048.0",
"archiver": "^8.0.0",
"aws-sdk": "^2.1502.0",
"axios": "^1.13.5",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^12.9.0",
Expand Down
1,334 changes: 220 additions & 1,114 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { S3 } from 'aws-sdk';
import Workspace from '../../../lib/parser/WorkSpace';
import path from 'path';
import fs from 'fs/promises';
import { spawn } from 'child_process';

export function convertPPTToPDF(
name: string,
contents: S3.Body,
contents: Buffer | Uint8Array | string,
workspace: Workspace
): Promise<Buffer> {
return new Promise((resolve, reject) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { writeFile } from 'fs/promises';
import path from 'path';
import Workspace from '../../../lib/parser/WorkSpace';
import { S3 } from 'aws-sdk';
import { getPageCount } from '../../../lib/pdf/getPageCount';
import { convertPage } from '../../../lib/pdf/convertPage';
import { combineIntoHTML } from '../../../lib/pdf/combineIntoHTML';
Expand All @@ -11,7 +10,7 @@ import CardOption from '../../../lib/parser/Settings/CardOption';
interface ConvertPDFToImagesInput {
workspace: Workspace;
noLimits: boolean;
contents?: S3.Body;
contents?: Buffer | Uint8Array | string;
name?: string;
settings?: CardOption;
}
Expand Down
125 changes: 65 additions & 60 deletions src/lib/storage/StorageHandler.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import aws from 'aws-sdk';
import { ObjectList } from 'aws-sdk/clients/s3';
import {
S3Client,
DeleteObjectCommand,
GetObjectCommand,
ListObjectsV2Command,
PutObjectCommand,
type _Object,
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

export interface StoredObject {
Body: Buffer | undefined;
}

class StorageHandler {
s3: aws.S3;
s3: S3Client;

constructor() {
// Set S3 endpoint to DigitalOcean Spaces. SPACES_ENDPOINT is a
// required production env var; asserting it matches how
// DigitalOcean Spaces endpoint. SPACES_ENDPOINT is a required
// production env var; the non-null assertion matches how
// SPACES_DEFAULT_BUCKET_NAME is treated below.
// NOSONAR — non-null assertion is intentional
const spacesEndpoint = new aws.Endpoint(process.env.SPACES_ENDPOINT!);
this.s3 = new aws.S3({
endpoint: spacesEndpoint,
this.s3 = new S3Client({
endpoint: process.env.SPACES_ENDPOINT!,
region: process.env.SPACES_REGION ?? 'us-east-1',
});
}

Expand All @@ -37,11 +48,13 @@ class StorageHandler {
}

async delete(key: string): Promise<boolean> {
const { s3 } = this;
try {
await s3
.deleteObject({ Bucket: StorageHandler.DefaultBucketName(), Key: key })
.promise();
await this.s3.send(
new DeleteObjectCommand({
Bucket: StorageHandler.DefaultBucketName(),
Key: key,
})
);
return true;
} catch (err) {
console.info('Delete file failed');
Expand All @@ -50,23 +63,26 @@ class StorageHandler {
}
}

async getContents(maxKeys: number = 1000): Promise<ObjectList | undefined> {
const { s3 } = this;
async getContents(maxKeys: number = 1000): Promise<_Object[] | undefined> {
console.debug('getting max', maxKeys, 'keys');
const files = [];
const files: _Object[] = [];
try {
let continuationToken: string | undefined;
let hasMore = true;
while (hasMore) {
const objects = await s3
.listObjects({
const objects = await this.s3.send(
new ListObjectsV2Command({
Bucket: StorageHandler.DefaultBucketName(),
MaxKeys: maxKeys,
ContinuationToken: continuationToken,
})
.promise();
);
if (objects.Contents) {
files.push(...objects.Contents);
}
hasMore = files.length < maxKeys && Boolean(objects.IsTruncated);
continuationToken = objects.NextContinuationToken;
hasMore =
files.length < maxKeys && Boolean(objects.IsTruncated) && continuationToken != null;
}
} catch (err) {
console.info('Get contents failed');
Expand All @@ -77,56 +93,45 @@ class StorageHandler {
return files;
}

getFileContents(key: string): Promise<aws.S3.GetObjectOutput> {
const { s3 } = this;
return new Promise<aws.S3.GetObjectOutput>((resolve, reject) => {
s3.getObject(
{ Bucket: StorageHandler.DefaultBucketName(), Key: key },
(err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
}
);
});
async getFileContents(key: string): Promise<StoredObject> {
const response = await this.s3.send(
new GetObjectCommand({
Bucket: StorageHandler.DefaultBucketName(),
Key: key,
})
);
if (response.Body == null) {
return { Body: undefined };
}
const bytes = await response.Body.transformToByteArray();
return { Body: Buffer.from(bytes) };
}

uploadFile(
name: string,
data: Buffer | string
): Promise<aws.S3.PutObjectOutput> {
const { s3 } = this;

return new Promise<aws.S3.PutObjectOutput>((resolve, reject) => {
s3.putObject(
{
async uploadFile(name: string, data: Buffer | string): Promise<void> {
try {
await this.s3.send(
new PutObjectCommand({
Bucket: StorageHandler.DefaultBucketName(),
Key: name,
Body: data,
},
(err, response) => {
if (err) {
console.info('Upload file failed');
console.error(err);
reject(err);
} else {
resolve(response);
}
}
})
);
});
} catch (err) {
console.info('Upload file failed');
console.error(err);
throw err;
}
}

getPresignedUrl(key: string, expiresSeconds = 3600): Promise<string> {
return new Promise((resolve, reject) => {
this.s3.getSignedUrl(
'getObject',
{ Bucket: StorageHandler.DefaultBucketName(), Key: key, Expires: expiresSeconds },
(err, url) => (err ? reject(err) : resolve(url))
);
});
return getSignedUrl(
this.s3,
new GetObjectCommand({
Bucket: StorageHandler.DefaultBucketName(),
Key: key,
}),
{ expiresIn: expiresSeconds }
);
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/lib/zip/zip.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { strFromU8, unzipSync } from 'fflate';
import { Body } from 'aws-sdk/clients/s3';
import { renderToStaticMarkup } from 'react-dom/server';
import { getUploadLimits } from '../misc/getUploadLimits';
import {
Expand All @@ -16,7 +15,7 @@ import { convertImageToHTML } from '../../infrastracture/adapters/fileConversion

interface File {
name: string;
contents?: Body | string;
contents?: Buffer | Uint8Array | string;
}

class ZipHandler {
Expand Down
7 changes: 3 additions & 4 deletions src/services/DownloadService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { S3 } from 'aws-sdk';
import StorageHandler from '../lib/storage/StorageHandler';
import DownloadRepository from '../data_layer/DownloadRepository';

Expand All @@ -9,7 +8,7 @@
owner: string,
key: string,
storage: StorageHandler
): Promise<S3.Body | null | undefined> {
): Promise<Buffer | null | undefined> {
const fileEntry = await this.downloadRepository.getFile(owner, key);
if (!fileEntry) {
return null;
Expand All @@ -27,8 +26,8 @@
}

isMissingDownloadError(error: unknown) {
const errorName = (error as AWS.AWSError)?.name;
return errorName?.match(/NoSuchKey/);
const errorName = (error as { name?: string })?.name;
return errorName != null && errorName.includes('NoSuchKey');

Check warning on line 30 in src/services/DownloadService.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=2anki_server&issues=AZ4tIhH_yBuujp-YKFk2&open=AZ4tIhH_yBuujp-YKFk2&pullRequest=2293
}

deleteMissingFile(owner: string, key: string) {
Expand Down
3 changes: 1 addition & 2 deletions src/usecases/uploads/getPackagesFromZip.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Body } from 'aws-sdk/clients/s3';
import CardOption from '../../lib/parser/Settings/CardOption';
import { ZipHandler } from '../../lib/zip/zip';
import { PrepareDeck } from '../../infrastracture/adapters/fileConversion/PrepareDeck';
Expand All @@ -12,7 +11,7 @@ import { getRelevantFiles } from './getRelevantFiles';
import { enableMarkdownForMarkdownUploads } from './enableMarkdownForMarkdownUploads';

export const getPackagesFromZip = async (
fileContents: Body | undefined,
fileContents: Buffer | Uint8Array | string | undefined,
paying: boolean,
settings: CardOption,
workspace: Workspace,
Expand Down