Skip to content

Commit 011dcc1

Browse files
sending attached documents through firebase
1 parent 9eebc54 commit 011dcc1

File tree

12 files changed

+9650
-10095
lines changed

12 files changed

+9650
-10095
lines changed

apps/api/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"dependencies": {
2828
"@nestjs/cache-manager": "^2.2.2",
2929
"@nestjs/common": "^10.0.0",
30-
"@nestjs/config": "^3.2.3",
30+
"@nestjs/config": "^3.3.0",
3131
"@nestjs/core": "^10.0.0",
3232
"@nestjs/jwt": "^10.2.0",
3333
"@nestjs/passport": "^10.0.3",
@@ -46,6 +46,7 @@
4646
"class-validator": "^0.14.1",
4747
"cloudinary": "^1.41.3",
4848
"dotenv": "^16.4.5",
49+
"firebase": "^11.5.0",
4950
"lodash": "^4.17.21",
5051
"multer-storage-cloudinary": "^4.0.0",
5152
"nodemailer": "^6.9.15",

apps/api/src/app.module.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ import { SendGridModule } from './sendgrid/sendgrid.module';
1414
import { ChatGateway } from './chat/chat.gateway';
1515
import { ChatModule } from './chat/chat.module';
1616
import { RoomsModule } from './chat/rooms.module';
17+
import { FirebaseModule } from './firebase/firebase.module';
18+
import firebaseConfig from './config/firebase.config';
1719

1820
@Module({
1921
imports: [
2022
ConfigModule.forRoot({
2123
isGlobal: true,
22-
load: [typeorm],
24+
load: [typeorm, firebaseConfig],
2325
envFilePath: '.env',
2426
}),
2527
TypeOrmModule.forRootAsync({
@@ -45,6 +47,7 @@ import { RoomsModule } from './chat/rooms.module';
4547
SendGridModule,
4648
ChatModule,
4749
RoomsModule,
50+
FirebaseModule,
4851
],
4952
controllers: [AppController],
5053
providers: [

apps/api/src/chat/chat.controller.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { ChatService } from './chat.service';
1616
import { UsersService } from '../users/users.service';
1717
import { JwtAuthGuard } from '@/auth/jwt.auth.guard';
1818
import { FileInterceptor } from '@nestjs/platform-express';
19-
import { CloudinaryService } from '../cloudinary/cloudinary.service';
19+
import { FirebaseService } from '../firebase/firebase.service';
2020
import { ApiConsumes, ApiBody } from '@nestjs/swagger';
2121
import { SendMessageDto } from './dto/send-message.dto';
2222

@@ -25,7 +25,7 @@ export class ChatController {
2525
constructor(
2626
private readonly chatService: ChatService,
2727
private readonly userService: UsersService,
28-
private readonly cloudinaryService: CloudinaryService
28+
private readonly firebaseService: FirebaseService
2929
) {}
3030

3131
@Post(':receiverId/send')
@@ -104,9 +104,9 @@ export class ChatController {
104104
@Body('messageId') messageId: string
105105
) {
106106
try {
107-
// Upload file to Cloudinary
108-
const uploadResult = await this.cloudinaryService.uploadFile(file);
109-
console.log('File uploaded to Cloudinary:', uploadResult);
107+
// Upload file to Firebase instead of Cloudinary
108+
const uploadResult = await this.firebaseService.uploadFile(file);
109+
console.log('File uploaded to Firebase:', uploadResult);
110110

111111
const sender = await this.userService.getAuthenticatedUser(req.user.id);
112112
const receiver = await this.userService.findById(receiverId);

apps/api/src/chat/chat.module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
1010
import { Room } from './entities/room.entity';
1111
import { ChatMessage } from './entities/chat-message.entity';
1212
import { RoomsModule } from './rooms.module';
13-
import { CloudinaryModule } from '@/cloudinary/cloudinary.module';
13+
import { FirebaseModule } from '@/firebase/firebase.module';
1414
import { MulterModule } from '@nestjs/platform-express';
1515

1616
@Module({
@@ -19,7 +19,7 @@ import { MulterModule } from '@nestjs/platform-express';
1919
UsersModule,
2020
AuthModule,
2121
RoomsModule,
22-
CloudinaryModule, // Add CloudinaryModule here
22+
FirebaseModule, // Use FirebaseModule instead of CloudinaryModule
2323
MulterModule.register(), // Register MulterModule for file uploads
2424
JwtModule.registerAsync({
2525
imports: [ConfigModule],

apps/api/src/cloudinary/cloudinary.service.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,34 @@ export class CloudinaryService {
4545
// Use original format to preserve file extension
4646
use_filename: true,
4747
unique_filename: true,
48+
// Flag as attachment for proper download handling
49+
attachment: true,
4850
// Add context for more info
4951
context: {
5052
original_filename: originalName,
5153
mime_type: file.mimetype
5254
}
5355
}, (error, result) => {
5456
if (error) return reject(error);
57+
58+
// For non-image files, ensure the URL includes the resource_type for proper handling
59+
if (resourceType !== 'image' && result) {
60+
// Extract the base URL without the /image/ path segment
61+
const urlParts = result.secure_url.split('/');
62+
const uploadIndex = urlParts.findIndex(part => part === 'upload');
63+
64+
if (uploadIndex !== -1) {
65+
// Reconstruct URL with proper resource_type
66+
urlParts.splice(uploadIndex, 0, resourceType);
67+
result.secure_url = urlParts.join('/');
68+
69+
// Ensure the URL includes the file extension for proper MIME type handling
70+
if (!result.secure_url.endsWith(fileExt) && fileExt) {
71+
result.secure_url = `${result.secure_url}${fileExt}`;
72+
}
73+
}
74+
}
75+
5576
resolve(result);
5677
}).end(file.buffer);
5778
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { registerAs } from '@nestjs/config';
2+
3+
export default registerAs('firebase', () => ({
4+
apiKey: process.env.FIREBASE_API_KEY,
5+
authDomain: process.env.FIREBASE_AUTH_DOMAIN,
6+
projectId: process.env.FIREBASE_PROJECT_ID,
7+
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
8+
messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
9+
appId: process.env.FIREBASE_APP_ID,
10+
measurementId: process.env.FIREBASE_MEASUREMENT_ID,
11+
}));
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Module } from '@nestjs/common';
2+
import { FirebaseService } from './firebase.service';
3+
4+
@Module({
5+
providers: [FirebaseService],
6+
exports: [FirebaseService],
7+
})
8+
export class FirebaseModule {}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { initializeApp } from 'firebase/app';
3+
import {
4+
getStorage,
5+
ref,
6+
uploadBytes,
7+
getDownloadURL,
8+
uploadString,
9+
UploadMetadata
10+
} from 'firebase/storage';
11+
import { ConfigService } from '@nestjs/config';
12+
13+
@Injectable()
14+
export class FirebaseService {
15+
private app;
16+
private storage;
17+
18+
constructor(private configService: ConfigService) {
19+
const firebaseConfig = {
20+
apiKey: this.configService.get<string>('firebase.apiKey'),
21+
authDomain: this.configService.get<string>('firebase.authDomain'),
22+
projectId: this.configService.get<string>('firebase.projectId'),
23+
storageBucket: this.configService.get<string>('firebase.storageBucket'),
24+
messagingSenderId: this.configService.get<string>('firebase.messagingSenderId'),
25+
appId: this.configService.get<string>('firebase.appId'),
26+
measurementId: this.configService.get<string>('firebase.measurementId')
27+
};
28+
29+
this.app = initializeApp(firebaseConfig);
30+
this.storage = getStorage(this.app);
31+
}
32+
33+
async uploadFile(file: Express.Multer.File): Promise<{ secure_url: string, originalname: string, mimetype: string }> {
34+
try {
35+
// Create a storage reference
36+
const timestamp = Date.now();
37+
const fileName = `${timestamp}_${file.originalname}`;
38+
const filePath = `mindsmesh-attachments/${fileName}`;
39+
const storageRef = ref(this.storage, filePath);
40+
41+
// Set metadata
42+
const metadata: UploadMetadata = {
43+
contentType: file.mimetype,
44+
customMetadata: {
45+
'originalname': file.originalname,
46+
},
47+
};
48+
49+
// Upload the file
50+
const snapshot = await uploadBytes(storageRef, file.buffer, metadata);
51+
52+
// Get the download URL
53+
const downloadURL = await getDownloadURL(snapshot.ref);
54+
55+
return {
56+
secure_url: downloadURL,
57+
originalname: file.originalname,
58+
mimetype: file.mimetype
59+
};
60+
} catch (error) {
61+
console.error('Error uploading file to Firebase:', error);
62+
throw error;
63+
}
64+
}
65+
}

apps/api/src/users/users.controller.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ export class UsersController {
236236
@UseInterceptors(
237237
FileFieldsInterceptor([
238238
{ name: 'avatar', maxCount: 1 },
239-
{ name: 'skillFiles', maxCount: 10 },
239+
{ name: 'skillImageUrls', maxCount: 10 },
240240
])
241241
)
242242
@ApiOperation({ summary: 'Update a user by ID' })
@@ -258,14 +258,23 @@ export class UsersController {
258258
@UploadedFiles()
259259
files: {
260260
avatar?: Express.Multer.File[];
261-
skillFiles?: Express.Multer.File[];
261+
skillImageUrls?: Express.Multer.File[];
262262
}
263263
): Promise<UserResponseDto> {
264264
// Optional: Log incoming files for debugging
265-
console.log('Received skillFiles:', files.skillFiles);
265+
console.log('Received skillImageUrls:', files.skillImageUrls);
266266
console.log('Received avatar:', files.avatar);
267267

268-
const updatedUser = await this.usersService.update(id, userDto, files.skillFiles);
268+
if (files.skillImageUrls && files.skillImageUrls.length > 0) {
269+
console.log('Received skillImageUrls:', files.skillImageUrls);
270+
// Use uploadFile instead of uploadImage to preserve file extensions
271+
const uploadResults = await Promise.all(
272+
files.skillImageUrls.map((file) => this.cloudinaryService.uploadFile(file))
273+
);
274+
userDto.skillImageUrls = uploadResults.map((result) => result.secure_url);
275+
}
276+
277+
const updatedUser = await this.usersService.update(id, userDto, files.skillImageUrls);
269278
return plainToClass(UserResponseDto, updatedUser);
270279
}
271280

apps/api/src/users/users.service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export class UsersService {
172172
return users;
173173
}
174174

175-
async update(id: string, updateUserDto: UpdateUserDto, skillFiles?: Express.Multer.File[]): Promise<User> {
175+
async update(id: string, updateUserDto: UpdateUserDto, skillImageUrls?: Express.Multer.File[]): Promise<User> {
176176
const user = await this.usersRepository.findOne({
177177
where: { id },
178178
relations: ['skills'],
@@ -210,9 +210,9 @@ export class UsersService {
210210
}
211211

212212
// Handle new skill images
213-
if (skillFiles && skillFiles.length > 0) {
213+
if (skillImageUrls && skillImageUrls.length > 0) {
214214
const uploadedUrls = await Promise.all(
215-
skillFiles.map(file => this.cloudinaryService.uploadImage(file))
215+
skillImageUrls.map(file => this.cloudinaryService.uploadFile(file))
216216
);
217217

218218
// Filter out any failed uploads

0 commit comments

Comments
 (0)