kodadot/nft-gallery

View on GitHub
composables/massmint/useZipValidator.ts

Summary

Maintainability
A
3 hrs
Test Coverage
import { ref } from 'vue'
import type { ZipEntry } from 'unzipit'
import { unzip } from 'unzipit'
import { mimeTypes } from '@kodadot1/static'
import { MAX_UPLOADED_FILE_SIZE } from '@/utils/constants'

export interface FileObject {
  imageUrl: string
  file: File
}

export interface WarningObject {
  name: string
  reason: string
}

export interface ValidityResult {
  validFiles: FileObject[]
  totalFiles: number
  warnings: WarningObject[]
}

export const validFormats = [
  'bmp',
  'gif',
  'jpg',
  'jpeg',
  'png',
  'svg',
  'tiff',
  'webp',
  'mp4',
  'ogv',
  'mov',
  'qt',
  'webm',
  'glb',
  'gltf',
  'flac',
  'mp3',
  'json',
]

const toMegaBytes = (bytes: number) => bytes / Math.pow(1024, 2)

const isFileSizeTooLarge = (size: number): boolean =>
  toMegaBytes(size) > MAX_UPLOADED_FILE_SIZE

const getFileExtension = (name: string): string => {
  const lastDotIndex = name.lastIndexOf('.')
  return lastDotIndex !== -1
    ? name.substring(lastDotIndex + 1).toLowerCase()
    : ''
}

const isMacOsHiddenFile = (name: string): boolean => name.startsWith('__MACOSX')

const isValidFileExtension = (extension: string): boolean =>
  validFormats.includes(extension)

async function checkZipFileValidity(entries: {
  [key: string]: ZipEntry
}): Promise<ValidityResult> {
  const validFiles: FileObject[] = []
  const warnings: WarningObject[] = []

  for (const [name, entry] of Object.entries(entries)) {
    let isEntryValid = true
    if (isMacOsHiddenFile(name)) {
      isEntryValid = false
    }

    if (isEntryValid && entry.isDirectory) {
      warnings.push({
        name,
        reason: 'is a directory',
      })
      isEntryValid = false
    }

    if (isEntryValid && isFileSizeTooLarge(entry.size)) {
      warnings.push({
        name,
        reason: 'File size exceeds maximum limit',
      })
      isEntryValid = false
    }

    const fileExtension = getFileExtension(name)
    if (isEntryValid && !isValidFileExtension(fileExtension)) {
      warnings.push({
        name,
        reason: `Invalid file format (${fileExtension})`,
      })
      isEntryValid = false
    }

    if (isEntryValid) {
      const blob = await entry.blob(mimeTypes[fileExtension])
      const file: FileObject = {
        imageUrl: URL.createObjectURL(blob),
        file: new File([blob], name, { type: blob.type }),
      }
      validFiles.push(file)
    }
  }

  return {
    validFiles,
    totalFiles: Object.keys(entries).length,
    warnings,
  }
}

export function useZipFileValidator(zipFilePath: string) {
  const { $consola } = useNuxtApp()

  const validFiles = ref<FileObject[]>([])
  const warnings = ref<WarningObject[]>([])
  const loading = ref<boolean>(true)
  const totalFiles = ref<number>(0)
  const allValid = ref<boolean>(false)

  const processZipFile = async () => {
    const { entries } = await unzip(zipFilePath)
    const {
      validFiles: validFilesValue,
      warnings: warningsValue,
      totalFiles: totalFilesValue,
    } = await checkZipFileValidity(entries)
    validFiles.value = validFilesValue
    warnings.value = warningsValue
    totalFiles.value = totalFilesValue
    loading.value = false
    allValid.value = validFilesValue.length === totalFilesValue
  }

  processZipFile().catch((error) => {
    $consola.error('Error processing zip file:', error)
  })

  return { validFiles, warnings, loading, totalFiles, allValid }
}