diff --git a/backend/pom.xml b/backend/pom.xml index 3594ce4..d5490ca 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -121,6 +121,11 @@ runtime + + software.amazon.awssdk + s3 + 2.20.26 + diff --git a/backend/src/main/java/com/amigoscode/Main.java b/backend/src/main/java/com/amigoscode/Main.java index 9efcc7b..4c41367 100644 --- a/backend/src/main/java/com/amigoscode/Main.java +++ b/backend/src/main/java/com/amigoscode/Main.java @@ -3,6 +3,8 @@ import com.amigoscode.customer.Customer; import com.amigoscode.customer.CustomerRepository; import com.amigoscode.customer.Gender; +import com.amigoscode.s3.S3Buckets; +import com.amigoscode.s3.S3Service; import com.github.javafaker.Faker; import com.github.javafaker.Name; import org.springframework.boot.CommandLineRunner; @@ -11,6 +13,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.security.crypto.password.PasswordEncoder; +import java.io.IOException; import java.util.Random; import java.util.UUID; @@ -26,23 +29,38 @@ CommandLineRunner runner( CustomerRepository customerRepository, PasswordEncoder passwordEncoder) { return args -> { - var faker = new Faker(); - Random random = new Random(); - Name name = faker.name(); - String firstName = name.firstName(); - String lastName = name.lastName(); - int age = random.nextInt(16, 99); - Gender gender = age % 2 == 0 ? Gender.MALE : Gender.FEMALE; - String email = firstName.toLowerCase() + "." + lastName.toLowerCase() + "@amigoscode.com"; - Customer customer = new Customer( - firstName + " " + lastName, - email, - passwordEncoder.encode("password"), - age, - gender); - customerRepository.save(customer); - System.out.println(email); + createRandomCustomer(customerRepository, passwordEncoder); + // testBucketUploadAndDownload(s3Service, s3Buckets); }; } + private static void testBucketUploadAndDownload(S3Service s3Service, S3Buckets s3Buckets) throws IOException { + s3Service.putObject( + s3Buckets.getCustomer(), + "foo", + "Hello World".getBytes()); + byte[] obj = s3Service.getObject("fs-mehul-customer-test", "foo"); + + System.out.println("Horrayfwfw: " + new String(obj)); + } + + private static void createRandomCustomer(CustomerRepository customerRepository, PasswordEncoder passwordEncoder) { + var faker = new Faker(); + Random random = new Random(); + Name name = faker.name(); + String firstName = name.firstName(); + String lastName = name.lastName(); + int age = random.nextInt(16, 99); + Gender gender = age % 2 == 0 ? Gender.MALE : Gender.FEMALE; + String email = firstName.toLowerCase() + "." + lastName.toLowerCase() + "@amigoscode.com"; + Customer customer = new Customer( + firstName + " " + lastName, + email, + passwordEncoder.encode("password"), + age, + gender); + customerRepository.save(customer); + System.out.println(email); + } + } diff --git a/backend/src/main/java/com/amigoscode/customer/Customer.java b/backend/src/main/java/com/amigoscode/customer/Customer.java index 05eccf8..72d7506 100644 --- a/backend/src/main/java/com/amigoscode/customer/Customer.java +++ b/backend/src/main/java/com/amigoscode/customer/Customer.java @@ -17,6 +17,10 @@ @UniqueConstraint( name = "customer_email_unique", columnNames = "email" + ), + @UniqueConstraint( + name = "profile_image_id_unique", + columnNames = "profileImageId" ) } ) @@ -57,6 +61,11 @@ public class Customer implements UserDetails { ) private String password; + @Column( + unique = true + ) + private String profileImageId; + public Customer() { } @@ -74,6 +83,17 @@ public Customer(Integer id, this.gender = gender; } + public Customer(Integer id, + String name, + String email, + String password, + Integer age, + Gender gender, + String profileImageId) { + this(id, name, email, password, age, gender); + this.profileImageId = profileImageId; + } + public Customer(String name, String email, String password, @@ -126,28 +146,13 @@ public void setGender(Gender gender) { this.gender = gender; } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Customer customer = (Customer) o; - return Objects.equals(id, customer.id) && Objects.equals(name, customer.name) && Objects.equals(email, customer.email) && Objects.equals(age, customer.age) && gender == customer.gender; - } - @Override - public int hashCode() { - return Objects.hash(id, name, email, age, gender); + public String getProfileImageId() { + return profileImageId; } - @Override - public String toString() { - return "Customer{" + - "id=" + id + - ", name='" + name + '\'' + - ", email='" + email + '\'' + - ", age=" + age + - ", gender=" + gender + - '}'; + public void setProfileImageId(String profileImageId) { + this.profileImageId = profileImageId; } @Override @@ -184,4 +189,30 @@ public boolean isCredentialsNonExpired() { public boolean isEnabled() { return true; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Customer customer = (Customer) o; + return Objects.equals(id, customer.id) && Objects.equals(name, customer.name) && Objects.equals(email, customer.email) && Objects.equals(age, customer.age) && gender == customer.gender && Objects.equals(password, customer.password) && Objects.equals(profileImageId, customer.profileImageId); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, email, age, gender, password, profileImageId); + } + + @Override + public String toString() { + return "Customer{" + + "id=" + id + + ", name='" + name + '\'' + + ", email='" + email + '\'' + + ", age=" + age + + ", gender=" + gender + + ", password='" + password + '\'' + + ", profileImageId='" + profileImageId + '\'' + + '}'; + } } diff --git a/backend/src/main/java/com/amigoscode/customer/CustomerController.java b/backend/src/main/java/com/amigoscode/customer/CustomerController.java index a5abe1d..d462df5 100644 --- a/backend/src/main/java/com/amigoscode/customer/CustomerController.java +++ b/backend/src/main/java/com/amigoscode/customer/CustomerController.java @@ -2,8 +2,10 @@ import com.amigoscode.jwt.JWTUtil; import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -54,4 +56,20 @@ public void updateCustomer( customerService.updateCustomer(customerId, updateRequest); } + @PostMapping( + value = "{customerId}/profile-image", + consumes = MediaType.MULTIPART_FORM_DATA_VALUE + ) + public void uploadCustomerProfileImage( + @PathVariable("customerId") Integer customerId, + @RequestParam("file") MultipartFile file) { + customerService.uploadCustomerProfileImage(customerId, file); + } + + @GetMapping(value = "{customerId}/profile-image", produces = MediaType.IMAGE_JPEG_VALUE) + public byte[] getuploadCustomerProfileImage( + @PathVariable("customerId") Integer customerId) { + return customerService.getCustomerProfileImage(customerId); + } + } diff --git a/backend/src/main/java/com/amigoscode/customer/CustomerDTO.java b/backend/src/main/java/com/amigoscode/customer/CustomerDTO.java index 21b0a2a..63bd4e6 100644 --- a/backend/src/main/java/com/amigoscode/customer/CustomerDTO.java +++ b/backend/src/main/java/com/amigoscode/customer/CustomerDTO.java @@ -9,7 +9,7 @@ public record CustomerDTO ( Gender gender, Integer age, List roles, - String username -){ + String username, + String profileImageId){ } diff --git a/backend/src/main/java/com/amigoscode/customer/CustomerDTOMapper.java b/backend/src/main/java/com/amigoscode/customer/CustomerDTOMapper.java index b01d52d..9e122f8 100644 --- a/backend/src/main/java/com/amigoscode/customer/CustomerDTOMapper.java +++ b/backend/src/main/java/com/amigoscode/customer/CustomerDTOMapper.java @@ -20,7 +20,8 @@ public CustomerDTO apply(Customer customer) { .stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList()), - customer.getUsername() + customer.getUsername(), + customer.getProfileImageId() ); } } diff --git a/backend/src/main/java/com/amigoscode/customer/CustomerDao.java b/backend/src/main/java/com/amigoscode/customer/CustomerDao.java index a0bda72..fdd7836 100644 --- a/backend/src/main/java/com/amigoscode/customer/CustomerDao.java +++ b/backend/src/main/java/com/amigoscode/customer/CustomerDao.java @@ -5,11 +5,12 @@ public interface CustomerDao { List selectAllCustomers(); - Optional selectCustomerById(Integer id); + Optional selectCustomerById(Integer customerId); void insertCustomer(Customer customer); boolean existsCustomerWithEmail(String email); - boolean existsCustomerById(Integer id); + boolean existsCustomerById(Integer customerId); void deleteCustomerById(Integer customerId); void updateCustomer(Customer update); Optional selectUserByEmail(String email); + void updateCustomerProfileImageId(String profileImageId, Integer customerId); } diff --git a/backend/src/main/java/com/amigoscode/customer/CustomerJDBCDataAccessService.java b/backend/src/main/java/com/amigoscode/customer/CustomerJDBCDataAccessService.java index 3dffcd3..b5032a6 100644 --- a/backend/src/main/java/com/amigoscode/customer/CustomerJDBCDataAccessService.java +++ b/backend/src/main/java/com/amigoscode/customer/CustomerJDBCDataAccessService.java @@ -21,7 +21,7 @@ public CustomerJDBCDataAccessService(JdbcTemplate jdbcTemplate, @Override public List selectAllCustomers() { var sql = """ - SELECT id, name, email, password, age, gender + SELECT id, name, email, password, age, gender, profile_image_id FROM customer LIMIT 1000 """; @@ -32,7 +32,7 @@ public List selectAllCustomers() { @Override public Optional selectCustomerById(Integer id) { var sql = """ - SELECT id, name, email, password, age, gender + SELECT id, name, email, password, age, gender, profile_image_id FROM customer WHERE id = ? """; @@ -125,7 +125,7 @@ public void updateCustomer(Customer update) { @Override public Optional selectUserByEmail(String email) { var sql = """ - SELECT id, name, email, password, age, gender + SELECT id, name, email, password, age, gender, profile_image_id FROM customer WHERE email = ? """; @@ -133,4 +133,14 @@ public Optional selectUserByEmail(String email) { .stream() .findFirst(); } + + @Override + public void updateCustomerProfileImageId(String profileImageId, Integer customerId) { + var sql = """ + UPDATE customer + SET profile_image_id = ? + WHERE id = ? + """; + jdbcTemplate.update(sql, profileImageId, customerId); + } } diff --git a/backend/src/main/java/com/amigoscode/customer/CustomerJPADataAccessService.java b/backend/src/main/java/com/amigoscode/customer/CustomerJPADataAccessService.java index 7c08e70..80b1c08 100644 --- a/backend/src/main/java/com/amigoscode/customer/CustomerJPADataAccessService.java +++ b/backend/src/main/java/com/amigoscode/customer/CustomerJPADataAccessService.java @@ -57,4 +57,9 @@ public Optional selectUserByEmail(String email) { return customerRepository.findCustomerByEmail(email); } + @Override + public void updateCustomerProfileImageId(String profileImageId, Integer customerId) { + customerRepository.updateProfileImageId(profileImageId, customerId); + } + } diff --git a/backend/src/main/java/com/amigoscode/customer/CustomerListDataAccessService.java b/backend/src/main/java/com/amigoscode/customer/CustomerListDataAccessService.java index cbc138d..b628856 100644 --- a/backend/src/main/java/com/amigoscode/customer/CustomerListDataAccessService.java +++ b/backend/src/main/java/com/amigoscode/customer/CustomerListDataAccessService.java @@ -83,4 +83,9 @@ public Optional selectUserByEmail(String email) { .findFirst(); } + @Override + public void updateCustomerProfileImageId(String profileImageId, Integer customerId) { + // TODO: Implement this + } + } diff --git a/backend/src/main/java/com/amigoscode/customer/CustomerRepository.java b/backend/src/main/java/com/amigoscode/customer/CustomerRepository.java index 41e54be..78210a6 100644 --- a/backend/src/main/java/com/amigoscode/customer/CustomerRepository.java +++ b/backend/src/main/java/com/amigoscode/customer/CustomerRepository.java @@ -1,6 +1,8 @@ package com.amigoscode.customer; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; import java.util.Optional; @@ -10,4 +12,7 @@ public interface CustomerRepository boolean existsCustomerByEmail(String email); boolean existsCustomerById(Integer id); Optional findCustomerByEmail(String email); + @Modifying + @Query("UPDATE Customer c SET c.profileImageId = ?1 WHERE c.id = ?2") + int updateProfileImageId(String profileImageId, Integer customerId); } diff --git a/backend/src/main/java/com/amigoscode/customer/CustomerRowMapper.java b/backend/src/main/java/com/amigoscode/customer/CustomerRowMapper.java index c004b54..03ea033 100644 --- a/backend/src/main/java/com/amigoscode/customer/CustomerRowMapper.java +++ b/backend/src/main/java/com/amigoscode/customer/CustomerRowMapper.java @@ -16,6 +16,7 @@ public Customer mapRow(ResultSet rs, int rowNum) throws SQLException { rs.getString("email"), rs.getString("password"), rs.getInt("age"), - Gender.valueOf(rs.getString("gender"))); + Gender.valueOf(rs.getString("gender")), + rs.getString("profile_image_id")); } } diff --git a/backend/src/main/java/com/amigoscode/customer/CustomerService.java b/backend/src/main/java/com/amigoscode/customer/CustomerService.java index 05ecfa2..32cede9 100644 --- a/backend/src/main/java/com/amigoscode/customer/CustomerService.java +++ b/backend/src/main/java/com/amigoscode/customer/CustomerService.java @@ -3,11 +3,16 @@ import com.amigoscode.exception.DuplicateResourceException; import com.amigoscode.exception.RequestValidationException; import com.amigoscode.exception.ResourceNotFoundException; +import com.amigoscode.s3.S3Buckets; +import com.amigoscode.s3.S3Service; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; @Service @@ -16,13 +21,19 @@ public class CustomerService { private final CustomerDao customerDao; private final CustomerDTOMapper customerDTOMapper; private final PasswordEncoder passwordEncoder; + private final S3Service s3Service; + private final S3Buckets s3Buckets; public CustomerService(@Qualifier("jdbc") CustomerDao customerDao, CustomerDTOMapper customerDTOMapper, - PasswordEncoder passwordEncoder) { + PasswordEncoder passwordEncoder, + S3Service s3Service, + S3Buckets s3Buckets) { this.customerDao = customerDao; this.customerDTOMapper = customerDTOMapper; this.passwordEncoder = passwordEncoder; + this.s3Service = s3Service; + this.s3Buckets = s3Buckets; } public List getAllCustomers() { @@ -61,13 +72,17 @@ public void addCustomer(CustomerRegistrationRequest customerRegistrationRequest) } public void deleteCustomerById(Integer customerId) { + checkIfCustomerExistsOrThrow(customerId); + + customerDao.deleteCustomerById(customerId); + } + + private void checkIfCustomerExistsOrThrow(Integer customerId) { if (!customerDao.existsCustomerById(customerId)) { throw new ResourceNotFoundException( "customer with id [%s] not found".formatted(customerId) ); } - - customerDao.deleteCustomerById(customerId); } public void updateCustomer(Integer customerId, @@ -106,5 +121,45 @@ public void updateCustomer(Integer customerId, customerDao.updateCustomer(customer); } + + public void uploadCustomerProfileImage(Integer customerId, MultipartFile file) { + checkIfCustomerExistsOrThrow(customerId); + String profileImageId = UUID.randomUUID().toString(); + try { + s3Service.putObject( + s3Buckets.getCustomer(), "profile-images/%s/%s".formatted(customerId, profileImageId), + file.getBytes() + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + + customerDao.updateCustomerProfileImageId(profileImageId, customerId); + } + + public byte[] getCustomerProfileImage(Integer customerId) { + var customer = customerDao.selectCustomerById(customerId) + .map(customerDTOMapper) + .orElseThrow(() -> new ResourceNotFoundException( + "customer with id [%s] not found".formatted(customerId) + )); + + // TODO: check if profileImageId is empty or null + + if (customer.profileImageId().isBlank()) { + throw new ResourceNotFoundException("customer with id [%s] profile image not found".formatted(customerId)); + } + + byte[] profileImage = null; + try { + profileImage = s3Service.getObject( + s3Buckets.getCustomer(), + "profile-images/%s/%s".formatted(customerId, customer.profileImageId()) + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + return profileImage; + } } diff --git a/backend/src/main/java/com/amigoscode/s3/S3Buckets.java b/backend/src/main/java/com/amigoscode/s3/S3Buckets.java new file mode 100644 index 0000000..9d174a6 --- /dev/null +++ b/backend/src/main/java/com/amigoscode/s3/S3Buckets.java @@ -0,0 +1,18 @@ +package com.amigoscode.s3; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "aws.s3.buckets") +public class S3Buckets { + private String customer; + + public String getCustomer() { + return customer; + } + + public void setCustomer(String customer) { + this.customer = customer; + } +} diff --git a/backend/src/main/java/com/amigoscode/s3/S3Config.java b/backend/src/main/java/com/amigoscode/s3/S3Config.java new file mode 100644 index 0000000..1b0b725 --- /dev/null +++ b/backend/src/main/java/com/amigoscode/s3/S3Config.java @@ -0,0 +1,24 @@ +package com.amigoscode.s3; + + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +@Configuration +public class S3Config { + + @Value("${aws.region}") + private String awsRegion; + + @Bean + public S3Client s3Client() { + S3Client client = S3Client.builder() + .region(Region.of(awsRegion)) + .build(); + + return client; + } +} diff --git a/backend/src/main/java/com/amigoscode/s3/S3Service.java b/backend/src/main/java/com/amigoscode/s3/S3Service.java new file mode 100644 index 0000000..d7e01fb --- /dev/null +++ b/backend/src/main/java/com/amigoscode/s3/S3Service.java @@ -0,0 +1,45 @@ +package com.amigoscode.s3; + +import org.springframework.stereotype.Service; +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +import java.io.IOException; + +@Service +public class S3Service { + + private final S3Client s3; + + public S3Service(S3Client s3Client) { + this.s3 = s3Client; + } + + public void putObject(String bucketName, String key, byte[] file) { + PutObjectRequest objectRequest = PutObjectRequest.builder() + .bucket(bucketName) + .key(key) + .build(); + s3.putObject(objectRequest, RequestBody.fromBytes(file)); + } + + public byte[] getObject(String bucketName, String key) throws IOException { + GetObjectRequest getObjectRequest = GetObjectRequest.builder() + .bucket(bucketName) + .key(key) + .build(); + ResponseInputStream res = s3.getObject(getObjectRequest); + + try { + return res.readAllBytes(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + + } +} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index d3fbbe6..b968caa 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -9,6 +9,12 @@ cors: allowed-headers: "*" exposed-headers: "*" +aws: + region: us-east-1 + s3: + buckets: + customer: fs-mehul-customer-test + management: endpoints: web: diff --git a/backend/src/main/resources/db/migration/V2__ADD_Customer_Profile_Image.sql b/backend/src/main/resources/db/migration/V2__ADD_Customer_Profile_Image.sql new file mode 100644 index 0000000..3d355e2 --- /dev/null +++ b/backend/src/main/resources/db/migration/V2__ADD_Customer_Profile_Image.sql @@ -0,0 +1,6 @@ +ALTER TABLE customer +ADD COLUMN profile_image_id VARCHAR(36); + +ALTER TABLE customer +ADD CONSTRAINT profile_image_id_unique +UNIQUE (profile_image_id) ; \ No newline at end of file diff --git a/backend/src/test/java/com/amigoscode/customer/CustomerRowMapperTest.java b/backend/src/test/java/com/amigoscode/customer/CustomerRowMapperTest.java index 81fd38f..86e6ebd 100644 --- a/backend/src/test/java/com/amigoscode/customer/CustomerRowMapperTest.java +++ b/backend/src/test/java/com/amigoscode/customer/CustomerRowMapperTest.java @@ -22,6 +22,8 @@ void mapRow() throws SQLException { when(resultSet.getString("name")).thenReturn("Jamila"); when(resultSet.getString("email")).thenReturn("jamila@gmail.com"); when(resultSet.getString("gender")).thenReturn("FEMALE"); + when(resultSet.getString("password")).thenReturn("password"); + when(resultSet.getString("profile_image_id")).thenReturn("22222"); // When Customer actual = customerRowMapper.mapRow(resultSet, 1); @@ -29,7 +31,7 @@ void mapRow() throws SQLException { // Then Customer expected = new Customer( 1, "Jamila", "jamila@gmail.com", "password", 19, - Gender.FEMALE); + Gender.FEMALE, "22222"); assertThat(actual).isEqualTo(expected); } } \ No newline at end of file diff --git a/backend/src/test/java/com/amigoscode/customer/CustomerServiceTest.java b/backend/src/test/java/com/amigoscode/customer/CustomerServiceTest.java index 87190c4..80b7634 100644 --- a/backend/src/test/java/com/amigoscode/customer/CustomerServiceTest.java +++ b/backend/src/test/java/com/amigoscode/customer/CustomerServiceTest.java @@ -3,6 +3,8 @@ import com.amigoscode.exception.DuplicateResourceException; import com.amigoscode.exception.RequestValidationException; import com.amigoscode.exception.ResourceNotFoundException; +import com.amigoscode.s3.S3Buckets; +import com.amigoscode.s3.S3Service; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -24,12 +26,16 @@ class CustomerServiceTest { private CustomerDao customerDao; @Mock private PasswordEncoder passwordEncoder; + @Mock + private S3Service s3Service; + @Mock + private S3Buckets s3Buckets; private CustomerService underTest; private final CustomerDTOMapper customerDTOMapper = new CustomerDTOMapper(); @BeforeEach void setUp() { - underTest = new CustomerService(customerDao, customerDTOMapper, passwordEncoder); + underTest = new CustomerService(customerDao, customerDTOMapper, passwordEncoder, s3Service, s3Buckets); } @Test diff --git a/backend/src/test/java/com/amigoscode/journey/CustomerIT.java b/backend/src/test/java/com/amigoscode/journey/CustomerIT.java index 0770f4e..f7c4f21 100644 --- a/backend/src/test/java/com/amigoscode/journey/CustomerIT.java +++ b/backend/src/test/java/com/amigoscode/journey/CustomerIT.java @@ -84,7 +84,8 @@ void canRegisterCustomer() { gender, age, List.of("ROLE_USER"), - email + email, + null ); assertThat(allCustomers).contains(expectedCustomer); @@ -266,7 +267,7 @@ void canUpdateCustomer() { .getResponseBody(); CustomerDTO expected = new CustomerDTO( - id, newName, email, gender, age, List.of("ROLE_USER"), email + id, newName, email, gender, age, List.of("ROLE_USER"), email, null ); assertThat(updatedCustomer).isEqualTo(expected); diff --git a/docker-compose.yml b/docker-compose.yml index 56e9949..594b5d0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,30 +13,6 @@ services: networks: - db restart: unless-stopped - amigoscode-api: - container_name: amigoscode-api - image: amigoscode/amigoscode-api - environment: - SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/customer - ports: - - "8088:8080" - networks: - - db - depends_on: - - db - restart: unless-stopped - amigoscode-react: - container_name: amigoscode-react - image: amigoscode/amigoscode-react - build: - context: frontend/react - args: - api_base_url: http://localhost:8088 - ports: - - "3000:5173" - depends_on: - - amigoscode-api - restart: unless-stopped networks: db: diff --git a/frontend/react/package-lock.json b/frontend/react/package-lock.json index 4c6eef4..4bfdf82 100644 --- a/frontend/react/package-lock.json +++ b/frontend/react/package-lock.json @@ -17,6 +17,7 @@ "jwt-decode": "^3.1.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", "react-icons": "^4.7.1", "react-router-dom": "^6.8.0", "yup": "^0.32.11" @@ -2252,6 +2253,14 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", + "engines": { + "node": ">=4" + } + }, "node_modules/axios": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.2.tgz", @@ -2517,6 +2526,17 @@ "node": ">=0.8.0" } }, + "node_modules/file-selector": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", + "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -3023,6 +3043,22 @@ "react": "^18.2.0" } }, + "node_modules/react-dropzone": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", + "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", + "dependencies": { + "attr-accept": "^2.2.2", + "file-selector": "^0.6.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/react-fast-compare": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", diff --git a/frontend/react/package.json b/frontend/react/package.json index 96a94d3..f3a5f77 100644 --- a/frontend/react/package.json +++ b/frontend/react/package.json @@ -18,6 +18,7 @@ "jwt-decode": "^3.1.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", "react-icons": "^4.7.1", "react-router-dom": "^6.8.0", "yup": "^0.32.11" diff --git a/frontend/react/src/components/customer/CustomerCard.jsx b/frontend/react/src/components/customer/CustomerCard.jsx index 8e457ca..7b4ee0c 100644 --- a/frontend/react/src/components/customer/CustomerCard.jsx +++ b/frontend/react/src/components/customer/CustomerCard.jsx @@ -16,7 +16,7 @@ import { } from '@chakra-ui/react'; import {useRef} from 'react' -import {deleteCustomer} from "../../services/client.js"; +import {customerProfilePictureUrl, deleteCustomer} from "../../services/client.js"; import {errorNotification, successNotification} from "../../services/notification.js"; import UpdateCustomerDrawer from "./UpdateCustomerDrawer.jsx"; @@ -48,9 +48,7 @@ export default function CardWithImage({id, name, email, age, gender, imageNumber { // useField() returns [formik.getFieldProps(), formik.getFieldMeta()] @@ -23,10 +30,52 @@ const MyTextInput = ({label, ...props}) => { ); }; +const MyDropzone= ({ customerId, fetchCustomers }) => { + const onDrop = useCallback(acceptedFiles => { + const formData = new FormData(); + formData.append("file", acceptedFiles[0]) + uploadCustomerProfilePicture(customerId, formData).then(() => { + successNotification("Success", "Profile picture uploaded") + fetchCustomers(); + }).catch(() => { + errorNotification("Err", "Err") + }) + }, []) + const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop}) + + return ( + + + { + isDragActive ? + Drop the photo here ... : + Drag 'n' drop picture here, or click to select picture + } + + ) +} + // And now we can use these const UpdateCustomerForm = ({ fetchCustomers, initialValues, customerId }) => { return ( <> + + + + { } catch (e) { throw e; } -} \ No newline at end of file +} + +export const uploadCustomerProfilePicture = async (id, formData) => { + try { + return axios.post(`${import.meta.env.VITE_API_BASE_URL}/api/v1/customers/${id}/profile-image`, + formData, + { + ...getAuthConfig(), + 'Content-Type': 'multipart/form-data' + } + ); + } catch (e) { + throw e; + } +} + +export const customerProfilePictureUrl = (id) => + `${import.meta.env.VITE_API_BASE_URL}/api/v1/customers/${id}/profile-image` \ No newline at end of file
Drop the photo here ...
Drag 'n' drop picture here, or click to select picture