Skip to content

Commit

Permalink
Merge branch 'main' into feat/rotate-catalyst-servers-to-retry
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinszuchet authored Nov 18, 2024
2 parents 8dcd203 + 21f3f8e commit a798dde
Show file tree
Hide file tree
Showing 10 changed files with 4,521 additions and 10 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ contents
yarn-error.log
.env.local
*.log
api/src/migrations
api/src/migrations

cli/textures-output
cli/textures
25 changes: 19 additions & 6 deletions api/src/logic/backfills/open-for-business-backfill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ import { EthAddress } from '@dcl/schemas'

type BackfillData = {
progress: {
completedAt: number
completedAt?: number
collectionCreatedAt?: number
storeCreatedAt?: number
}
}

function validateOpenForBusinessBackfillData(data: BackfillData): boolean {
return Number.isInteger(data.progress.completedAt)
const { completedAt, collectionCreatedAt, storeCreatedAt } = data.progress

return [completedAt, collectionCreatedAt, storeCreatedAt].every(
(value) => value === undefined || Number.isInteger(value)
)
}

export function mergeOpenForBusinessProgress(
Expand All @@ -31,10 +37,17 @@ export function mergeOpenForBusinessProgress(
}

try {
userProgress.progress.steps = 2
userProgress.progress.store_completed = true
userProgress.progress.collection_submitted = true
userProgress.completed_at = Math.min(userProgress.completed_at || Date.now(), backfillData.progress.completedAt)
const { completedAt, collectionCreatedAt, storeCreatedAt } = backfillData.progress

if (!userProgress.completed_at) {
userProgress.progress.steps = [collectionCreatedAt, storeCreatedAt].filter(Boolean).length
userProgress.progress.store_completed = !!storeCreatedAt
userProgress.progress.collection_submitted = !!collectionCreatedAt
}

if (completedAt) {
userProgress.completed_at = Math.min(userProgress.completed_at || Date.now(), completedAt)
}

return userProgress
} catch (error) {
Expand Down
1 change: 0 additions & 1 deletion api/src/logic/backfills/unique-event-backfill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { EthAddress } from '@dcl/schemas'

type BackfillData = {
progress: {
step: number
completedAt: number
}
}
Expand Down
18 changes: 18 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Command-Line Interface

This workspace provides various methods for managing badges. Below is a list of available actions and instructions on how to execute them within this project.

## Upload a badge texture

This action uploads badge textures. Upon running the command, it will prompt the following:
* The directory path where badge textures are stored (_directory containing directories with different badges' textures_)
* AWS Programmatic User Access Keys

Before proceeding, ensure that the badge texture directories follow the service’s naming and structural conventions:

- **Directory Name**: It should match the badge ID, using lowercase letters and snake_case (e.g., `all_day_adventurer`).
- **Directory Structure**:
- Each directory should contain two subdirectories named `2d` and `3d` for storing textures in each format.
- The `2d` subdirectory must contain a single file named `normal.png`.
- The `3d` subdirectory should include three files: `basecolor.png`, `hrm.png`, and `normal.png`.

4 changes: 3 additions & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
},
"dependencies": {
"@aws-sdk/client-sns": "^3.600.0",
"aws-sdk": "^2.1646.0",
"aws-sdk": "^2.1692.0",
"mime-types": "^2.1.35",
"prompts": "^2.4.2"
},
"devDependencies": {
"@dcl/eslint-config": "^2.1.0",
"@types/mime-types": "^2.1.4",
"@types/node": "^20.14.5",
"@types/prompts": "^2.4.9",
"@well-known-components/test-helpers": "^1.5.6",
Expand Down
6 changes: 5 additions & 1 deletion cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ dotenv.config({ path: './.env.local' })

import * as Events from './events'
import { publishEvent } from './publisher'
import { uploadTextures } from './textures-manager'

async function askUser(choices: { title: string; value: string }[]) {
return await prompts({
Expand All @@ -17,7 +18,7 @@ async function askUser(choices: { title: string; value: string }[]) {
async function main() {
const choices = [
{ title: 'Publish Move to Parcel event', value: 'move_to_parcel' },
{ title: 'Publish Test event', value: 'test' },
{ title: 'Upload badge textures', value: 'upload_textures' },
{ title: 'Exit', value: 'exit' }
]

Expand All @@ -28,6 +29,9 @@ async function main() {
case 'move_to_parcel':
await publishEvent(JSON.stringify(Events.MoveToParcelEvent))
break
case 'upload_textures':
await uploadTextures()
break
case 'test':
await publishEvent(JSON.stringify(Events.TestEvent))
break
Expand Down
170 changes: 170 additions & 0 deletions cli/src/textures-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import prompts from 'prompts'
import fs from 'fs'
import path from 'path'
import AWS from 'aws-sdk'
import mime from 'mime-types'

import { logAllFilesWithinDirectory, parseTexturesDirectoryName, trimBadgeName } from './utils'

const outputDirectory = path.join(__dirname, '../../textures-output')

const renameAndCopyFiles = (srcDir: string, destDir: string, badgeName: string) => {
fs.readdir(srcDir, (err, files) => {
if (err) throw err

files.forEach((file) => {
const srcFilePath = path.join(srcDir, file)
const stat = fs.statSync(srcFilePath)

if (stat.isDirectory()) {
const trimmedName = trimBadgeName(file, badgeName)
let newDirectoryName: string = parseTexturesDirectoryName(trimmedName)

const match = trimmedName.match(/2d|3d/i) // Check for "2d" or "3d"
if (match) {
newDirectoryName = match[0].toLowerCase() // Force lowercase "2d" or "3d"
console.log(`Matched special directory name: ${newDirectoryName}`) // Debug log
}

// If the directory is called "base", rename it to "starter"
if (newDirectoryName === 'base') {
newDirectoryName = 'starter'
}

const newDestDir = path.join(destDir, newDirectoryName)

fs.mkdirSync(newDestDir, { recursive: true })

// Recursively process the directory
renameAndCopyFiles(srcFilePath, newDestDir, badgeName)
} else {
if (file !== '.DS_Store') {
let newFileName = file

if (newFileName.includes('_basecolor')) {
newFileName = 'basecolor.png'
} else if (newFileName.includes('_hrm')) {
newFileName = 'hrm.png'
} else if (newFileName.includes('_normal') || newFileName.match(/\.png$/)) {
newFileName = 'normal.png'
}

const destFilePath = path.join(destDir, newFileName)

// Copy the file to the new location with the new name
fs.copyFileSync(srcFilePath, destFilePath)
}
}
})
})
}

const processDirectory = (srcRoot: string, destRoot: string) => {
fs.mkdirSync(destRoot, { recursive: true })

fs.readdir(srcRoot, (err, directories) => {
if (err) throw err

directories.forEach((dir) => {
const srcDirPath = path.join(srcRoot, dir)
const stat = fs.statSync(srcDirPath)

if (stat.isDirectory()) {
const newDirName = parseTexturesDirectoryName(dir)
const newDestDirPath = path.join(destRoot, newDirName)

fs.mkdirSync(newDestDirPath, { recursive: true })

// Process each subdirectory within the badge directory
renameAndCopyFiles(srcDirPath, newDestDirPath, dir)
}
})
})
}

export async function uploadTextures() {
const directory = await prompts({
type: 'text',
name: 'src',
message: 'Enter the directory holding the textures (src):'
})

processDirectory(directory.src, outputDirectory)
logAllFilesWithinDirectory(outputDirectory)

const shouldWeContinue = await prompts({
type: 'confirm',
name: 'continue',
message: 'Do you want to continue?'
})

if (!shouldWeContinue.continue) {
console.log('Removing textures output directory...')
fs.rmdirSync(outputDirectory, { recursive: true })
return
} else {
// select bucket from two options
const selection = await prompts({
type: 'select',
name: 'bucket',
message: 'Select the S3 bucket environment:',
choices: [
{ title: 'prd', value: 'assets-cdn-decentraland-org-contentbucket-6898728' },
{ title: 'dev', value: 'assets-cdn-decentraland-zone-contentbucket-79cb984' }
]
})

const awsAccessKeyId = await prompts({
type: 'text',
name: 'key',
message: 'Enter your AWS Access Key ID:'
})

const awsSecretAccessKey = await prompts({
type: 'text',
name: 'key',
message: 'Enter your AWS Secret Access Key:'
})

const s3 = new AWS.S3({
accessKeyId: awsAccessKeyId.key,
secretAccessKey: awsSecretAccessKey.key,
region: 'us-east-1' // default region, do not change
})

await uploadDirectory(s3, selection.bucket, outputDirectory)
}
}

const uploadDirectory = async (s3Client: AWS.S3, bucketName: string, dirPath: string, s3Folder: string = '') => {
console.log('Uploading textures...')

const files = fs.readdirSync(dirPath)

for (const fileName of files) {
if (fileName && fileName === '.DS_Store') {
continue
}

const fullPath = path.join(dirPath, fileName)
const fileStats = fs.statSync(fullPath)

if (fileStats.isDirectory()) {
// Recursively upload inner directories
await uploadDirectory(s3Client, bucketName, fullPath, `${s3Folder}${fileName}/`)
} else {
const fileContent = fs.readFileSync(fullPath)
const mimeType = mime.lookup(fullPath) || 'application/octet-stream'

const params = {
Bucket: bucketName,
Key: `${s3Folder}${fileName}`,
Body: fileContent,
ContentType: mimeType
}

await s3Client.upload(params).promise()
console.log(`Uploaded ${fileName} to ${bucketName}/${s3Folder}${fileName}`)
}
}
}
1 change: 1 addition & 0 deletions cli/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './textures-manager-utils'
43 changes: 43 additions & 0 deletions cli/src/utils/textures-manager-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import fs from 'fs'
import path from 'path'

function is2dOr3dDirectoryName(directoryName: string): boolean {
return (
directoryName === '2d' ||
directoryName === '3d' ||
directoryName.includes('2d') ||
(directoryName.includes('3d') &&
(directoryName.includes('starter') ||
directoryName.includes('bronze') ||
directoryName.includes('silver') ||
directoryName.includes('gold') ||
directoryName.includes('platinum') ||
directoryName.includes('diamond') ||
directoryName.includes('base')))
)
}

export function parseTexturesDirectoryName(directoryName: string): string {
return is2dOr3dDirectoryName(directoryName)
? directoryName
: directoryName
.replace(/([a-z])([A-Z])/g, '$1_$2') // Convert PascalCase to snake_case
.replace(/([a-zA-Z])(\d)/g, '$1_$2') // Add underscore between letters and numbers
.replace(/(\d)([a-zA-Z])/g, '$1_$2') // Add underscore between numbers and letters
.toLowerCase()
}

export function trimBadgeName(name: string, badgeName: string): string {
return name.replace(new RegExp(`^${badgeName}`, 'i'), '').toLowerCase()
}

export function logAllFilesWithinDirectory(directoryPath: string): void {
fs.readdirSync(directoryPath).forEach((file) => {
if (fs.statSync(path.join(directoryPath, file)).isDirectory()) {
console.log(`Directory: ${file}`)
logAllFilesWithinDirectory(path.join(directoryPath, file))
} else {
console.log(`File: ${file}`)
}
})
}
Loading

0 comments on commit a798dde

Please sign in to comment.