Skip to content
This repository has been archived by the owner on Mar 23, 2024. It is now read-only.

Commit

Permalink
Switched images storing to S3
Browse files Browse the repository at this point in the history
  • Loading branch information
nmashchenko committed Jun 6, 2023
1 parent b80eebf commit e7b17b4
Show file tree
Hide file tree
Showing 16 changed files with 248 additions and 101 deletions.
4 changes: 2 additions & 2 deletions client/src/components/Forms/TeamForm/TeamForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ function TeamForm() {
<UserImg
src={
member?.image
? LOCAL_PATH + '/' + member.image
? member.image
: 'https://i.pinimg.com/474x/41/26/bd/4126bd6b08769ed2c52367fa813c721e.jpg'
}
/>
Expand All @@ -135,7 +135,7 @@ function TeamForm() {
<CircleContainer>
<Text>{team.name}</Text>
</CircleContainer>
<TeamImgBorder src={team?.image ? LOCAL_PATH + '/' + team.image : tempImg} />
<TeamImgBorder src={team?.image ? team.image : tempImg} />
<Text fontSize="16px" fontWeight="400">
Creation date: {createDate}
</Text>
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/NavBar/Profile/Profile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const changeData = (data) => {
userRealName: data.fullName,
userUsername: data.username,
notificationBell: true,
userImg: LOCAL_PATH + '/' + data.image,
userImg: data.image,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const ProfileDetails = () => {
<Information>
<LeftCard>
<ImgContainer>
<UserAvatar src={LOCAL_PATH + '/' + user?.image} width={'9.375rem'} height={'9.375rem'} />
<UserAvatar src={user?.image} width={'9.375rem'} height={'9.375rem'} />
<AvatarEditButton onClick={() => navigate('/profile-edit')} />
</ImgContainer>
<TextContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ const ProfileForm = () => {
<Information>
<LeftCard>
<ImgContainer>
<Img src={LOCAL_PATH + '/' + user.image} />
<Img src={user.image} />
<EditUserDetails onClick={stopEditing}>
<EditIcon />
</EditUserDetails>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const InviteMembersForm = () => {
return (
<InvitedUser key={member?.id}>
<UsernameAvatarContainer>
<UserAvatar src={LOCAL_PATH + '/' + member?.image} />
<UserAvatar src={member?.image} />
<UserText>{member?.username}</UserText>
</UsernameAvatarContainer>
{index === 0 ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const UserCard = React.forwardRef((props, ref) => {
<UserInformationContainer>
{/* TODO: Change for real image! */}
<div>
<UserImage src={LOCAL_PATH + '/' + person.image} alt="User's image" />
<UserImage src={person.image} alt="User's image" />
</div>
{programmingLanguages}
</UserInformationContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const UserProfile = ({ user, handleClose }) => {
<ProfileContainer>
<LinksAndAvatarContainer>
<div>
<UserAvatar src={LOCAL_PATH + '/' + user.image} alt="avatar"></UserAvatar>
<UserAvatar src={user.image} alt="avatar"></UserAvatar>
</div>
{/* TODO: Change for real links! & rewrite for the .map() */}
<UserLink>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const UserProfilePhone = ({ user, mobileProfile, handleClose }) => {
</ComebackContainer>
<UserInformationContainer>
<div>
<UserImage src={LOCAL_PATH + '/' + user.image} alt="User's image" />
<UserImage src={user.image} alt="User's image" />
</div>
<UserInfoTextContainer>
<Text fontSize="16px" textAlign="start">
Expand Down
5 changes: 1 addition & 4 deletions client/src/shared/components/AutocompleteInput/List.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ export const List = ({ props, option }) => {
<li {...props}>
<Grid container alignItems="center">
<Grid item sx={{ display: 'flex', width: 38 }}>
<img
style={{ width: '30px', height: '30px', borderRadius: '50%' }}
src={LOCAL_PATH + '/' + option.image}
/>
<img style={{ width: '30px', height: '30px', borderRadius: '50%' }} src={option.image} />
</Grid>
<Grid item sx={{ width: 'calc(100% - 44px)', wordWrap: 'break-word' }}>
<h3 style={{ fontSize: '18px', color: 'white', fontWeight: 400 }}>{option.username}</h3>
Expand Down
162 changes: 110 additions & 52 deletions server/src/files/file.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import {
DeleteObjectCommand,
DeleteObjectsCommand,
ListObjectsCommand,
PutObjectCommand,
S3Client,
} from '@aws-sdk/client-s3';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as fs from 'fs';
import * as path from 'path';
import { promisify } from 'util';
import * as uuid from 'uuid';

export enum FileType {
Expand All @@ -27,30 +30,49 @@ export class FileService {

constructor(private readonly configService: ConfigService) {}

/**
* This function creates a file with a unique name and path, uploads it to an S3 bucket, and returns
* the URL of the uploaded file.
* @param {FileType} type - FileType enum, which specifies the type of file being created (e.g. users,
* teams, text)
* @param {string} file - The actual file content, either as a string or a base64 encoded string.
* @param {string} bucket - The name of the Amazon S3 bucket where the file will be uploaded.
* @returns a Promise that resolves to a string which is the URL of the uploaded file on Amazon S3.
*/
async createFile(
type: FileType,
file: Express.Multer.File | string,
file: string,
bucket: string,
): Promise<string> {
try {
/* Creating a unique file name and a file path. */
const fileName = uuid.v4() + '.jpg';
const filePath = path.resolve(__dirname, '..', 'static', type);
let fileExtension = '.jpg';

/* Creating a folder if it doesn't exist and then writing the file to the folder. */
if (!fs.existsSync(filePath)) {
fs.mkdirSync(filePath, { recursive: true });
switch (type) {
case FileType.USERS:
case FileType.TEAMS:
fileExtension = '.jpg';
break;
case FileType.TEXT:
fileExtension = '.txt';
break;
default:
throw new Error('Invalid file type');
}

/* This is checking if the file is a string or not. If it is not a string, then it will use 7bit encoding. */
if (typeof file !== 'string') {
fs.writeFileSync(path.resolve(filePath, fileName), file.buffer);
return type + '/' + fileName;
}
/* Creating a unique file name and a file path. */
const fileName = uuid.v4() + fileExtension;

const key = `${type}/${fileName}`;

/* If file is string it will convert it to Buffer and then use to write the file */
const buffer = Buffer.from(file, 'base64');
fs.writeFileSync(path.resolve(filePath, fileName), buffer);
return type + '/' + fileName;
const buffer =
type === FileType.TEXT
? Buffer.from(file)
: Buffer.from(file, 'base64');

await this.uploadToS3(key, buffer, bucket);

return `https://${bucket}.s3.amazonaws.com/${type}/${fileName}`;
} catch (err) {
throw new HttpException(
err.message,
Expand All @@ -60,16 +82,31 @@ export class FileService {
}

/**
* It removes a file from the server
* @param {string} fileName - The name of the file to be deleted.
* This function deletes a file from an S3 bucket using AWS SDK for JavaScript.
* @param {string} key - The key is a unique identifier for the object in the S3 bucket. It is used to
* retrieve or delete the object from the bucket.
* @param {string} bucket - The name of the S3 bucket from which the object needs to be deleted.
*/
async removeFile(fileName: string): Promise<void> {
async removeFromS3(key: string, bucket: string): Promise<void> {
const params = {
Bucket: bucket,
Key: key,
};

try {
const filePath = path.resolve(__dirname, '..', 'static', fileName);
await fs.promises.unlink(filePath);
} catch (err) {
console.log(
await this.s3Client.send(new DeleteObjectCommand(params)),
);
console.log(
`File ${key} successfully deleted from S3 bucket ${bucket}`,
);
} catch (error) {
console.error(
`Error deleting file ${key} from S3 bucket ${bucket}:`,
error,
);
throw new HttpException(
err.message,
`Error deleting file ${key} from S3 bucket ${bucket}`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
Expand All @@ -83,42 +120,63 @@ export class FileService {
* to Amazon S3. A `Buffer` is a temporary holding spot for data being moved from one place to another.
* In this case, it is being used to hold the file data before it is uploaded
*/
async uploadToS3(fileName: string, file: Buffer): Promise<any> {
async uploadToS3(key: string, file: Buffer, bucket: string): Promise<any> {
await this.s3Client.send(
new PutObjectCommand({
Bucket: 't8s-betalist',
Key: fileName,
Bucket: bucket,
Key: key,
Body: file,
}),
);
}

/**
* Writes a file at a given path via a promise interface.
*
* @param {string} fileName
* @param {string} data
*
* @return {Promise<void>}
* This function deletes a folder and its contents from an S3 bucket using the AWS SDK for JavaScript.
* @param {string} folderPath - The path of the folder or prefix to be deleted from the S3 bucket.
* @param {string} bucket - The name of the S3 bucket from which the folder needs to be deleted.
*/
createTextFile = async (
fileName: string,
data: string,
): Promise<Buffer> => {
const filePath = path.resolve(__dirname, '..', 'static', FileType.TEXT);
/* Creating a folder if it doesn't exist and then writing the file to the folder. */
if (!fs.existsSync(filePath)) {
fs.mkdirSync(filePath, { recursive: true });
}

const writeFile = promisify(fs.writeFile);
async deleteFolderFromS3(
folderPath: string,
bucket: string,
): Promise<void> {
const params = {
Bucket: bucket,
Prefix: folderPath, // The folder or prefix to be deleted
};

await writeFile(path.resolve(filePath, fileName), data, 'utf8');
try {
const listObjectsResponse = await this.s3Client.send(
new ListObjectsCommand(params),
);

// Read the file as a buffer
const readFileAsync = promisify(fs.readFile);
const buffer = await readFileAsync(path.resolve(filePath, fileName));
if (listObjectsResponse.Contents) {
const deleteObjectsParams = {
Bucket: bucket,
Delete: {
Objects: listObjectsResponse.Contents.map(obj => ({
Key: obj.Key,
})),
Quiet: false,
},
};

return buffer;
};
await this.s3Client.send(
new DeleteObjectsCommand(deleteObjectsParams),
);
console.log(
`Folder ${folderPath} and its contents successfully deleted from S3 bucket ${bucket}`,
);
} else {
console.log(
`Folder ${folderPath} does not exist in S3 bucket ${bucket}`,
);
}
} catch (error) {
console.error(
`Error deleting folder ${folderPath} from S3 bucket ${bucket}:`,
error,
);
throw error;
}
}
}
2 changes: 2 additions & 0 deletions server/src/maintenance/maintenance.controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Controller, Get, Param } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { SkipThrottle } from '@nestjs/throttler';
import mongoose from 'mongoose';

import { StatusResponseDto } from './dto/status-response.dto';
import { MaintenanceService } from './maintenance.service';

@ApiTags('Maintenance')
@SkipThrottle()
@Controller('maintenance')
export class MaintenanceController {
constructor(private maintenanceService: MaintenanceService) {}
Expand Down
18 changes: 18 additions & 0 deletions server/src/maintenance/maintenance.data.ts

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions server/src/maintenance/maintenance.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

import { FileModule } from '@/files/file.module';
import { NotificationsModule } from '@/notifications/notifications.module';
import { RolesModule } from '@/roles/roles.module';
import { TeamsModule } from '@/teams/teams.module';
Expand All @@ -18,6 +19,7 @@ import { MaintenanceService } from './maintenance.service';
RolesModule,
TeamsModule,
NotificationsModule,
FileModule,
],
})
export class MaintenanceModule {}
Loading

0 comments on commit e7b17b4

Please sign in to comment.