diff --git a/src/modules/uploader/providers/s3.service.ts b/src/modules/uploader/providers/s3.service.ts index f7c204d..9bedb3b 100644 --- a/src/modules/uploader/providers/s3.service.ts +++ b/src/modules/uploader/providers/s3.service.ts @@ -13,28 +13,23 @@ export class S3Service { private readonly logger = new Logger(S3Service.name); private readonly s3Client: S3Client; private readonly bucketName: string; - private readonly region: string; // Region is often 'us-east-1' or similar for Liara, but depends on your config - private readonly endpointUrl: string; // Renamed 'url' to 'endpointUrl' for clarity + private readonly region: string; + private readonly endpointUrl: string; constructor(private readonly configService: ConfigService) { - // --- Configuration for Liara S3 --- this.bucketName = this.configService.getOrThrow('BUCKET_NAME'); - // Liara S3 usually requires a specific endpoint URL - this.endpointUrl = this.configService.getOrThrow('LIARA_S3_ENDPOINT'); - // The region for Liara S3 is often 'us-east-1' or just a placeholder, - // but we'll keep it configurable for consistency. + this.endpointUrl = this.configService.getOrThrow('PARSPACK_S3_ENDPOINT'); this.region = this.configService.getOrThrow('BUCKET_REGION'); this.s3Client = new S3Client({ - region: this.region, // Use the configured region - endpoint: this.endpointUrl, // This is the crucial change for Liara S3 - forcePathStyle: true, // Often required when using custom S3 endpoints like Liara + region: this.region, + endpoint: this.endpointUrl, + forcePathStyle: true, credentials: { accessKeyId: this.configService.getOrThrow('BUCKET_ACCESS_KEY'), secretAccessKey: this.configService.getOrThrow('BUCKET_SECRET_KEY'), }, }); - // ------------------------------------ } /** @@ -49,17 +44,14 @@ export class S3Service { try { const sanitizedMetadata = metadata ? Object.entries(metadata).reduce( - (acc, [key, value]) => { - // Encode all values to be ASCII-safe - acc[key] = encodeURIComponent(value); + (acc, [metaKey, value]) => { + // S3 user metadata must be US-ASCII; base64 keeps signing stable for Unicode names + acc[metaKey] = Buffer.from(value, 'utf8').toString('base64'); return acc; }, {} as Record, ) : undefined; - // NOTE: 'ACL: public-read' might not be supported or necessary for Liara S3, - // depending on your bucket configuration on Liara. - // If uploads fail, try removing this line. const command = new PutObjectCommand({ Bucket: this.bucketName, Key: key, @@ -71,9 +63,8 @@ export class S3Service { await this.s3Client.send(command); - // --- URL for Liara S3 --- - // The public URL for Liara S3 files is typically: ${endpoint}/${bucketName}/${key} - const url = `${this.endpointUrl}/${this.bucketName}/${key}`; + const encodedKey = key.split('/').map(segment => encodeURIComponent(segment)).join('/'); + const url = `${this.endpointUrl}/${this.bucketName}/${encodedKey}`; this.logger.log(`File uploaded to S3: ${key}`); @@ -88,8 +79,6 @@ export class S3Service { } } - // --- Other methods remain the same as they use the S3Client which is now configured for Liara --- - /** * Get file stream from S3 */ @@ -156,7 +145,7 @@ export class S3Service { generateFileKey(originalName: string, fileType: FileType): string { const timestamp = Date.now(); const randomSuffix = randomInt(1000000, 9999999); - const extension = originalName.split('.').pop(); + const extension = this.extractSafeExtension(originalName); const basePathMap: Record = { image: 'images', @@ -170,4 +159,21 @@ export class S3Service { const basePath = basePathMap[fileType]; return `${basePath}/${timestamp}-${randomSuffix}.${extension}`; } + + /** + * Extract a safe ASCII extension so Persian/spaced names never end up in the S3 object key. + */ + private extractSafeExtension(originalName: string): string { + const basename = originalName.replace(/\\/g, '/').split('/').pop() ?? 'file'; + const lastDot = basename.lastIndexOf('.'); + + if (lastDot > 0 && lastDot < basename.length - 1) { + const ext = basename.slice(lastDot + 1).toLowerCase(); + if (/^[a-z0-9]{1,16}$/.test(ext)) { + return ext; + } + } + + return 'bin'; + } } diff --git a/src/seeders/data/admins.data.ts b/src/seeders/data/admins.data.ts index c38e86d..3c98108 100644 --- a/src/seeders/data/admins.data.ts +++ b/src/seeders/data/admins.data.ts @@ -12,6 +12,13 @@ export const adminsData: AdminData[] = [ lastName: 'مرتضایی', roleName: 'admin', + }, + { + phone: '09121724095', + firstName: 'آقای', + lastName: 'نواعتقاد', + roleName: 'admin', + }, { phone: '09185290775', @@ -21,9 +28,9 @@ export const adminsData: AdminData[] = [ }, { - phone: '09129283395', - firstName: 'مهرداد', - lastName: 'مظفری', + phone: '09180000000', + firstName: 'ندا', + lastName: 'سعیدی', roleName: 'designer', },