Skip to content

Commit

Permalink
Merge pull request #254 from amjedidiah/fix/demo-fixes
Browse files Browse the repository at this point in the history
Fix/demo fixes
  • Loading branch information
MedAmine1212 authored Jan 31, 2025
2 parents b77719f + a5e51ce commit cb3c680
Show file tree
Hide file tree
Showing 22 changed files with 382 additions and 469 deletions.
45 changes: 28 additions & 17 deletions src/backend/lib/aws/aws-generate-signature-headers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { S3ClientConfig } from '@aws-sdk/client-s3'
import { createHash, createHmac } from 'crypto'
import { Provider } from '../../../shared/types/StorageSDK'
import { UpupProvider } from '../../../shared/types/StorageSDK'

function hmac(key: string | Buffer, message: string) {
return createHmac('sha256', key).update(message).digest()
Expand All @@ -27,6 +27,23 @@ function calculateMD5(content: string) {
return createHash('md5').update(content).digest('base64')
}

function getHost(
bucketName: string,
provider: UpupProvider,
{ endpoint, region }: Pick<S3ClientConfig, 'endpoint' | 'region'>,
) {
switch (provider) {
case UpupProvider.AWS:
return `${bucketName}.s3.${region}.amazonaws.com`
case UpupProvider.BackBlaze:
return (endpoint as string).split('https://')[1]
case UpupProvider.DigitalOcean:
return `${bucketName}.${region}.digitaloceanspaces.com`
default:
return ''
}
}

export default function awsGenerateSignatureHeaders(
corsConfig: string,
bucketName: string,
Expand All @@ -37,16 +54,10 @@ export default function awsGenerateSignatureHeaders(
}: S3ClientConfig & {
credentials?: any
},
provider: Provider,
provider: UpupProvider,
) {
const service = 's3'
const hostMap = {
[Provider.AWS]: `${bucketName}.s3.${region}.amazonaws.com`,
[Provider.BackBlaze]: (endpoint as string).split('https://')[1],
[Provider.DigitalOcean]: `${bucketName}.${region}.digitaloceanspaces.com`,
[Provider.Azure]: ``,
}
const host = hostMap[provider]
const host = getHost(bucketName, provider, { endpoint, region })

// Calculate Content-MD5
const contentMD5 = calculateMD5(corsConfig)
Expand All @@ -60,18 +71,18 @@ export default function awsGenerateSignatureHeaders(
const method = 'PUT'

const canonicalUriMap = {
[Provider.AWS]: '/',
[Provider.BackBlaze]: `/${bucketName}/`,
[Provider.DigitalOcean]: `/`,
[Provider.Azure]: ``,
[UpupProvider.AWS]: '/',
[UpupProvider.BackBlaze]: `/${bucketName}/`,
[UpupProvider.DigitalOcean]: `/`,
[UpupProvider.Azure]: ``,
}
const canonicalUri = canonicalUriMap[provider]

const canonicalQueryStringMap = {
[Provider.AWS]: 'cors=',
[Provider.BackBlaze]: 'cors=null',
[Provider.DigitalOcean]: 'cors=',
[Provider.Azure]: ``,
[UpupProvider.AWS]: 'cors=',
[UpupProvider.BackBlaze]: 'cors=null',
[UpupProvider.DigitalOcean]: 'cors=',
[UpupProvider.Azure]: ``,
}
const canonicalQueryString = canonicalQueryStringMap[provider]

Expand Down
21 changes: 14 additions & 7 deletions src/backend/lib/aws/s3/s3-generate-presigned-url.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import { PutObjectCommand, S3Client, _Error } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
import { v4 as uuid } from 'uuid'
import {
PresignedUrlResponse,
UploadError,
UploadErrorType,
} from '../../../../shared/types/StorageSDK'
import { S3PresignedUrlParams } from '../../../types'
import fileValidateParams from '../../files/file-validate-params'
import s3GenerateSignedUrl from './s3-generate-signed-url'
import s3UpdateCORS from './s3-update-cors'

const DEFAULT_EXPIRES_IN = 3600

function getUploadErrorParams(error: unknown) {
const message =
((error as _Error) || {}).Message || (error as Error).message
const errorType = (((error as _Error) || {}).Code ||
UploadErrorType.PRESIGNED_URL_ERROR) as UploadErrorType

return { message, errorType }
}

export default async function s3GeneratePresignedUrl({
fileParams,
bucketName: Bucket,
Expand All @@ -35,7 +46,7 @@ export default async function s3GeneratePresignedUrl({
const client = new S3Client(s3ClientConfig)

// Generate unique key for the file
const Key = `uploads/${Date.now()}-${fileName}`
const Key = `${uuid()}-${fileName}`

// Create PutObject command
const command = new PutObjectCommand({
Expand All @@ -52,7 +63,7 @@ export default async function s3GeneratePresignedUrl({
})

// Generate public URL (if bucket is public)
const publicUrl = uploadUrl.split(Key)[0] + Key
const publicUrl = await s3GenerateSignedUrl(s3ClientConfig, Key, Bucket)

return {
key: Key,
Expand All @@ -63,11 +74,7 @@ export default async function s3GeneratePresignedUrl({
} catch (error) {
if (error instanceof UploadError) throw error

const message =
((error as _Error) || {}).Message || (error as Error).message
const errorType = (((error as _Error) || {}).Code ||
UploadErrorType.PRESIGNED_URL_ERROR) as UploadErrorType

const { message, errorType } = getUploadErrorParams(error)
throw new UploadError(message, errorType, false, 500)
}
}
12 changes: 6 additions & 6 deletions src/backend/lib/aws/s3/s3-update-cors.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { S3ClientConfig } from '@aws-sdk/client-s3'
import {
Provider,
UploadError,
UploadErrorType,
UpupProvider,
} from '../../../../shared/types/StorageSDK'
import awsGenerateSignatureHeaders from '../aws-generate-signature-headers'

export default async function s3UpdateCORS(
origin: string,
bucketName: string,
config: S3ClientConfig,
provider: Provider,
provider: UpupProvider,
) {
const urlMap = {
[Provider.AWS]: `https://${bucketName}.s3.${config.region}.amazonaws.com/?cors`,
[Provider.BackBlaze]: `${config.endpoint}/${bucketName}/?cors=null`,
[Provider.DigitalOcean]: `https://${bucketName}.${config.region}.digitaloceanspaces.com/?cors`,
[Provider.Azure]: ``,
[UpupProvider.AWS]: `https://${bucketName}.s3.${config.region}.amazonaws.com/?cors`,
[UpupProvider.BackBlaze]: `${config.endpoint}/${bucketName}/?cors=null`,
[UpupProvider.DigitalOcean]: `https://${bucketName}.${config.region}.digitaloceanspaces.com/?cors`,
[UpupProvider.Azure]: ``,
}
const url = urlMap[provider]

Expand Down
3 changes: 2 additions & 1 deletion src/backend/lib/azure/azure-generate-sas-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
SASProtocol,
generateBlobSASQueryParameters,
} from '@azure/storage-blob'
import { v4 as uuid } from 'uuid'
import {
PresignedUrlResponse,
UploadError,
Expand Down Expand Up @@ -42,7 +43,7 @@ export default async function azureGenerateSasUrl({
await azureGetTemporaryCredentials(blobServiceClient)

const { name: fileName, type: contentType } = fileParams
const blobName = `uploads/${Date.now()}-${fileName}`
const blobName = `${uuid()}-${fileName}`

// Get container client
const containerClient =
Expand Down
4 changes: 2 additions & 2 deletions src/backend/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { S3ClientConfig } from '@aws-sdk/client-s3'
import { Provider } from '../shared/types/StorageSDK'
import { UpupProvider } from '../shared/types/StorageSDK'

export interface FileParams {
name: string
Expand All @@ -20,7 +20,7 @@ export type S3PresignedUrlParams = UrlParams & {
bucketName: string
s3ClientConfig: S3ClientConfig
origin: string
provider: Provider
provider: UpupProvider
}

export type AzureSasUrlParams = UrlParams & {
Expand Down
79 changes: 65 additions & 14 deletions src/frontend/UpupUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import React, {
forwardRef,
useEffect,
useImperativeHandle,
useMemo,
useState,
} from 'react'
import { v4 as uuidv4 } from 'uuid'
Expand Down Expand Up @@ -56,13 +57,33 @@ export interface UploadFilesRef {
dynamicUploadFiles: (files: File[]) => Promise<string[] | null>
}

type FileProgress = {
id: string
loaded: number
total: number
}

type FilesProgressMap = Record<string, FileProgress>

// Add this helper function at the top level
const createFileWithId = (file: File) => {
return Object.assign(file, {
id: `${file.name}-${file.size}-${file.lastModified}-${uuidv4()}`,
})
}

function getUniqueFilesByName(files: File[]) {
const uniqueFiles = new Map()

files.forEach(file => {
if (!uniqueFiles.has(file.name)) {
uniqueFiles.set(file.name, file)
}
})

return Array.from(uniqueFiles.values())
}

/**
*
* @param storageConfig storage configuration
Expand Down Expand Up @@ -118,17 +139,41 @@ export const UpupUploader: FC<
onFilesSelected,
)

const [progress, setProgress] = useState(0)
const [filesProgressMap, setFilesProgressMap] =
useState<FilesProgressMap>({} as FilesProgressMap)
useEffect(() => {
setFilesProgressMap(
selectedFiles.reduce((a, b) => {
a[b.name] = {
id: b.name,
loaded: 0,
total: b.size,
}
return a
}, {} as FilesProgressMap),
)
}, [selectedFiles.length])

const progress = useMemo(() => {
const filesProgressMapValues = Object.values(filesProgressMap)
if (!filesProgressMapValues.length) return 0

const loadedValues = filesProgressMapValues.reduce(
(a, b) => a + (b.loaded / b.total) * 100,
0,
)
return loadedValues / filesProgressMapValues.length
}, [filesProgressMap])

/**
* Check if file type is accepted
* @param file File to check
* @throws Error if file type is not accepted
*/
const validateFileType = (file: File) => {
if (!checkFileType(accept, file, baseConfigs?.onFileTypeMismatch)) {
if (!checkFileType(accept, file, baseConfigs.onFileTypeMismatch)) {
const error = new Error(`File type ${file.type} not accepted`)
baseConfigs?.onFileUploadFail?.(file, error)
baseConfigs.onFileUploadFail?.(file, error)
throw error
}
}
Expand All @@ -155,7 +200,7 @@ export const UpupUploader: FC<
}MB`,
)
files.forEach(
file => baseConfigs?.onFileUploadFail?.(file, error),
file => baseConfigs.onFileUploadFail?.(file, error),
)
throw error
}
Expand Down Expand Up @@ -183,7 +228,7 @@ export const UpupUploader: FC<
} catch (error) {
files.forEach(
file =>
baseConfigs?.onFileUploadFail?.(file, error as Error),
baseConfigs.onFileUploadFail?.(file, error as Error),
)
throw error
}
Expand Down Expand Up @@ -227,22 +272,28 @@ export const UpupUploader: FC<
processedFiles.map(file =>
sdk.upload(file, {
onFileUploadStart:
baseConfigs?.onFileUploadStart,
baseConfigs.onFileUploadStart,
onFileUploadProgress: (file, progress) => {
setProgress(progress.loaded)
baseConfigs?.onFileUploadProgress?.(
setFilesProgressMap(prev => ({
...prev,
[file.name]: {
...prev[file.name],
loaded: progress.loaded,
},
}))
baseConfigs.onFileUploadProgress?.(
file,
progress,
)
},
onFileUploadComplete:
baseConfigs?.onFileUploadComplete,
baseConfigs.onFileUploadComplete,
onFileUploadFail:
baseConfigs?.onFileUploadFail,
baseConfigs.onFileUploadFail,
onTotalUploadProgress: (
completedFiles: number,
) =>
baseConfigs?.onTotalUploadProgress?.(
baseConfigs.onTotalUploadProgress?.(
completedFiles,
processedFiles.length,
),
Expand All @@ -254,7 +305,7 @@ export const UpupUploader: FC<
const uploadedFileKeys = uploadResults.map(
result => result.key,
)
baseConfigs?.onAllUploadsComplete?.(uploadedFileKeys)
baseConfigs.onAllUploadsComplete?.(uploadedFileKeys)
resolve(uploadedFileKeys)
} catch (error) {
reject(error)
Expand Down Expand Up @@ -320,7 +371,7 @@ export const UpupUploader: FC<
}

useEffect(() => {
const newFiles = selectedFiles.filter(file =>
const newFiles = getUniqueFilesByName(selectedFiles).filter(file =>
checkFileSize(file, maxFileSize),
)
if (limit && newFiles.length > limit)
Expand Down Expand Up @@ -412,7 +463,7 @@ export const UpupUploader: FC<
// Add file removal handler
const handleFileRemove = (file: FileWithId) => {
setSelectedFiles(prev => prev.filter(f => f !== file))
baseConfigs?.onFileRemove?.(file)
baseConfigs.onFileRemove?.(file)
}

return mini ? (
Expand Down
Loading

0 comments on commit cb3c680

Please sign in to comment.