20 Commits

Author SHA1 Message Date
d869a18901 boost controller endpoint little fix
+ get rid of not used dependencies
2025-05-16 13:42:24 +02:00
Patryk
1d55f40753 add boostNotice function 2025-05-15 20:29:37 +02:00
3d205df038 few cosmetic fixes 2025-05-14 08:35:33 +02:00
Patryk
5ccfc6ba2c change repository to service in wishlist, add @Lazy 2025-05-13 20:54:44 +02:00
Patryk
f0e3a129d0 change repository to service in wishlist, add @Lazy 2025-05-13 20:54:39 +02:00
Patryk
cdd31fd6b7 init wishlist files 2025-05-12 19:57:00 +02:00
09c15e70d9 New way of categories transfer 2025-05-05 10:10:03 +02:00
3b85b12741 Few improvements such as
application.properties.prod file
new DTO for response when adding notice
2025-05-02 14:49:22 +02:00
039678b90a Categories as map 2025-04-28 18:37:41 +02:00
025f733362 added endpoint for variables 2025-04-28 15:58:59 +02:00
c2f74ab799 little fixes one more time 2025-04-28 15:44:35 +02:00
bf565178f6 fix of images deletion 2025-04-28 15:06:32 +02:00
7f8f13b115 Images are working but still there is need to add isImageMain flag to images. 2025-04-28 14:17:38 +02:00
6b5dded7f8 WIP for images 2025-04-24 07:34:53 +02:00
a3d3a01d3a docker implemntation WIP 2025-04-19 19:57:11 +02:00
4b5baaa7e3 dodawanie zdjęć i pobieranie ścieżek do nich
więcej kategorii
2025-04-18 15:07:03 +02:00
b25bd3dbe9 proper data flow 2025-04-18 09:55:59 +02:00
8fc8473962 constructor injection instead of field injection 2025-04-18 09:23:13 +02:00
e60f959dfb client get by id 2025-04-17 13:45:51 +02:00
ba7e82e90c get one notice by id 2025-04-17 12:00:36 +02:00
32 changed files with 942 additions and 132 deletions

7
Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM openjdk:21
WORKDIR /app
COPY target/ArtisanConnectBackend-0.0.1-SNAPSHOT.jar app/artisan.jar
ENTRYPOINT ["java","-jar","app/artisan.jar"]

View File

@@ -1,9 +1,28 @@
services:
postgres:
app:
container_name: artisan
build: .
depends_on:
- db
networks:
- artisan_network
ports:
- '8085:8080'
db:
container_name: db
image: 'postgres:latest'
environment:
- 'POSTGRES_DB=default'
- 'POSTGRES_DB=postgres'
- 'POSTGRES_PASSWORD=postgres'
- 'POSTGRES_USER=postgres'
ports:
- '5432:5432'
networks:
- artisan_network
networks:
artisan_network:
name: artisan_net
external: false

24
pom.xml
View File

@@ -10,7 +10,7 @@
</parent>
<groupId>_11.asktpk</groupId>
<artifactId>ArtisanConnectBackend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<version>1.0.0</version>
<name>ArtisanConnectBackend</name>
<description>ArtisanConnectBackend</description>
<url/>
@@ -34,31 +34,16 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-oauth2-client</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-docker-compose</artifactId>-->
<!-- <scope>runtime</scope>-->
<!-- <optional>true</optional>-->
<!-- </dependency>-->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
@@ -73,15 +58,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.security</groupId>-->
<!-- <artifactId>spring-security-test</artifactId>-->
<!-- <scope>test</scope>-->
<!-- </dependency>-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.3</version>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>

View File

@@ -1,13 +1,27 @@
package _11.asktpk.artisanconnectbackend;
import jakarta.annotation.PostConstruct;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
@SpringBootApplication
public class ArtisanConnectBackendApplication {
private final Environment environment;
public ArtisanConnectBackendApplication(Environment environment) {
this.environment = environment;
}
public static void main(String[] args) {
SpringApplication.run(ArtisanConnectBackendApplication.class, args);
}
@PostConstruct
public void logDataSourceUrl() {
System.out.println("Datasource URL: " + environment.getProperty("spring.datasource.url"));
}
}

View File

@@ -0,0 +1,35 @@
package _11.asktpk.artisanconnectbackend.config;
import _11.asktpk.artisanconnectbackend.dto.RequestResponseDTO;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
@Controller
public class CustomErrorController implements ErrorController {
@RequestMapping("/error")
public ResponseEntity<RequestResponseDTO> handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
int statusCode = Integer.parseInt(status.toString());
if (statusCode == HttpStatus.NOT_FOUND.value()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new RequestResponseDTO("Nie znaleziono zasobu. Sprawdź URL i spróbuj ponownie."));
} else if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new RequestResponseDTO("Wystąpił wewnętrzny błąd serwera. Spróbuj ponownie później."));
}
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new RequestResponseDTO("Wystąpił nieoczekiwany błąd."));
}
}

View File

@@ -1,9 +1,8 @@
package _11.asktpk.artisanconnectbackend.controller;
import _11.asktpk.artisanconnectbackend.dto.ClientDTO;
import _11.asktpk.artisanconnectbackend.repository.ClientRepository;
import _11.asktpk.artisanconnectbackend.service.ClientService;
import _11.asktpk.artisanconnectbackend.dto.ClientDTO;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@@ -19,11 +18,29 @@ public class ClientController {
this.clientService = clientService;
}
@GetMapping("/all")
@GetMapping("/get/all")
public List<ClientDTO> getAllClients() {
return clientService.getAllClients();
}
@GetMapping("/get/{id}")
public ResponseEntity getClientById(@PathVariable long id) {
if(clientService.getClientById(id) != null) {
return new ResponseEntity(clientService.getClientById(id), HttpStatus.OK);
} else {
return new ResponseEntity(HttpStatus.NOT_FOUND);
}
}
@PostMapping("/add")
public ResponseEntity addClient(@RequestBody ClientDTO clientDTO) {
if(clientService.clientExists(clientDTO.getId())) {
return new ResponseEntity<>(HttpStatus.CONFLICT);
} else {
return new ResponseEntity<>(clientService.addClient(clientDTO), HttpStatus.CREATED);
}
}
// TODO: do zrobienia walidacja danych
@PutMapping("/edit/{id}")
public ResponseEntity updateClient(@PathVariable("id") long id, @RequestBody ClientDTO clientDTO) {

View File

@@ -0,0 +1,93 @@
package _11.asktpk.artisanconnectbackend.controller;
import _11.asktpk.artisanconnectbackend.dto.RequestResponseDTO;
import _11.asktpk.artisanconnectbackend.service.ImageService;
import _11.asktpk.artisanconnectbackend.service.NoticeService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
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.*;
@RestController
@RequestMapping("/api/v1/images")
public class ImageController {
private final ImageService imageService;
private final NoticeService noticeService;
ImageController(ImageService imageService, NoticeService noticeService) {
this.imageService = imageService;
this.noticeService = noticeService;
}
@Value("${file.upload-dir}")
private String uploadDir;
@PostMapping("/upload/{id}")
public ResponseEntity<RequestResponseDTO> uploadImage(@RequestParam("file") MultipartFile file, @PathVariable("id") Long noticeId) {
try {
if(file.isEmpty()) {
return ResponseEntity.badRequest().body(new RequestResponseDTO("File is empty"));
}
if(!Objects.equals(file.getContentType(), "image/jpeg") && !Objects.equals(file.getContentType(), "image/png")) {
return ResponseEntity.badRequest().body(new RequestResponseDTO("File must be a JPEG or PNG image."));
}
if(noticeId == null || !noticeService.noticeExists(noticeId)) {
return ResponseEntity.badRequest().body(new RequestResponseDTO("Notice ID is invalid or does not exist."));
}
String newImageName = imageService.saveImageToStorage(uploadDir, file);
imageService.addImageNameToDB(newImageName, noticeId);
return ResponseEntity.ok(new RequestResponseDTO("Image uploaded successfully with new name: " + newImageName));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE).body(new RequestResponseDTO(e.getMessage()));
}
}
@GetMapping("/get/{filename}")
public ResponseEntity<Resource> getImage(@PathVariable String filename) {
try {
return ResponseEntity.ok()
.contentType(MediaType.IMAGE_JPEG)
.body(imageService.getImage(uploadDir, filename));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
}
@GetMapping("/list/{id}")
public ResponseEntity<List<String>> getImagesNamesList(@PathVariable("id") Long noticeId) {
if(noticeId == null) {
return ResponseEntity.badRequest().body(Collections.singletonList("Notice ID is invalid or does not exist."));
}
List<String> result;
try {
result = imageService.getImagesList(noticeId);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Collections.singletonList(e.getMessage()));
}
return ResponseEntity.ok(result);
}
@DeleteMapping("/delete/{filename}")
public ResponseEntity<RequestResponseDTO> deleteImage(@PathVariable("filename") String filename) {
if(filename == null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new RequestResponseDTO("Filename is empty."));
}
try {
imageService.deleteImage(uploadDir, filename);
return ResponseEntity.status(HttpStatus.OK).body(new RequestResponseDTO("Image deleted successfully."));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new RequestResponseDTO(e.getMessage()));
}
}
}

View File

@@ -1,10 +1,12 @@
package _11.asktpk.artisanconnectbackend.controller;
import _11.asktpk.artisanconnectbackend.dto.NoticeAdditionDTO;
import _11.asktpk.artisanconnectbackend.dto.NoticeBoostDTO;
import _11.asktpk.artisanconnectbackend.dto.RequestResponseDTO;
import _11.asktpk.artisanconnectbackend.service.ClientService;
import _11.asktpk.artisanconnectbackend.service.NoticeService;
import _11.asktpk.artisanconnectbackend.dto.NoticeDTO;
import jakarta.persistence.EntityNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@@ -15,31 +17,46 @@ import java.util.List;
@RequestMapping("/api/v1/notices")
@RestController
public class NoticeController {
@Autowired
private NoticeService noticeService;
private final NoticeService noticeService;
private final ClientService clientService;
@Autowired
private ClientService clientService;
public NoticeController(NoticeService noticeService, ClientService clientService) {
this.noticeService = noticeService;
this.clientService = clientService;
}
@GetMapping("/all")
@GetMapping("/get/all")
public List<NoticeDTO> getAllNotices() {
return noticeService.getAllNotices();
}
@GetMapping("/get/{id}")
public ResponseEntity<?> getNoticeById(@PathVariable long id) {
if (noticeService.noticeExists(id)) {
return ResponseEntity.ok(noticeService.getNoticeById(id));
} else {
return ResponseEntity.notFound().build();
}
}
@PostMapping("/add")
public ResponseEntity<String> addNotice(@RequestBody NoticeDTO dto) {
public ResponseEntity<NoticeAdditionDTO> addNotice(@RequestBody NoticeDTO dto) {
if (!clientService.clientExists(dto.getClientId())) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body("Nie znaleziono klienta o ID: " + dto.getClientId());
.body(new NoticeAdditionDTO("Nie znaleziono klienta o ID: " + dto.getClientId()));
}
noticeService.addNotice(noticeService.fromDTO(dto));
if (dto.getCategory() == null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new NoticeAdditionDTO("Nie ma takiej kategorii"));
}
dto.setPublishDate(java.time.LocalDateTime.now());
return ResponseEntity.status(HttpStatus.CREATED).body("Dodano ogłoszenie.");
Long newNoticeId = noticeService.addNotice(dto);
return ResponseEntity.status(HttpStatus.CREATED).body(new NoticeAdditionDTO(newNoticeId ,"Dodano ogłoszenie."));
}
// TODO: zamiast dodawać tutaj pętlą, musi to robić NoticeService, trzeba zaimplementować odpowienią metodę
@PostMapping("/bulk_add")
public ResponseEntity<String> addNotices(@RequestBody List<NoticeDTO> notices_list) {
@@ -56,49 +73,49 @@ public class NoticeController {
isError = true;
errors.add(dto.getClientId().toString());
} else {
if(!isError){
noticeService.addNotice(noticeService.fromDTO(dto));
if (!isError) {
noticeService.addNotice(dto);
}
}
}
if(isError) {
if (isError) {
return response.status(HttpStatus.BAD_REQUEST).body("Nie znaleziono klientów: " + errors);
}
return response;
}
@PutMapping("/edit/{id}")
public ResponseEntity<Object> editNotice(@PathVariable("id") long id, @RequestBody NoticeDTO dto) {
if (noticeService.noticeExists(id)) {
try {
return new ResponseEntity<>(noticeService.updateNotice(id, dto), HttpStatus.OK);
} catch (EntityNotFoundException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
@PutMapping("/edit/{id}")
public ResponseEntity<Object> editNotice(@PathVariable("id") long id, @RequestBody NoticeDTO dto) {
if (noticeService.noticeExists(id)) {
try {
return new ResponseEntity<>(noticeService.updateNotice(id, dto), HttpStatus.OK);
} catch (EntityNotFoundException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Nie znaleziono ogłoszenia o ID: " + id);
}
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Nie znaleziono ogłoszenia o ID: " + id);
}
@DeleteMapping("/delete/{id}")
public ResponseEntity<RequestResponseDTO> deleteNotice(@PathVariable("id") long id) {
if (noticeService.noticeExists(id)) {
noticeService.deleteNotice(id);
return ResponseEntity.status(HttpStatus.OK).body(new RequestResponseDTO("Pomyślnie usunięto ogłoszenie o ID: " + id));
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new RequestResponseDTO("Nie znaleziono ogłoszenia o ID: " + id));
}
}
@PostMapping("/boost/{id}")
public ResponseEntity<RequestResponseDTO> boostNotice(@PathVariable("id") long clientId, @RequestBody NoticeBoostDTO dto) {
if (!noticeService.isNoticeOwnedByClient(dto.getNoticeId(), clientId)) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new RequestResponseDTO("Ogłoszenie nie istnieje lub nie należy do zalogowanego klienta."));
}
noticeService.boostNotice(dto.getNoticeId());
return ResponseEntity.status(HttpStatus.OK).body(new RequestResponseDTO("Ogłoszenie zostało pomyślnie wypromowane."));
}
}
@DeleteMapping("/delete/{id}")
public ResponseEntity deleteNotice(@PathVariable("id") long id) {
if(noticeService.noticeExists(id)) {
noticeService.deleteNotice(id);
return new ResponseEntity<>(HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
// @GetMapping("/check/{id}")
// public ResponseEntity<String> checkNotice(@PathVariable("id") long id) {
// if (noticeService.noticeExists(id)) {
// return ResponseEntity.ok("Ogłoszenie o ID " + id + " istnieje.");
// } else {
// return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Nie znaleziono ogłoszenia o ID: " + id);
// }
// }
}

View File

@@ -0,0 +1,40 @@
package _11.asktpk.artisanconnectbackend.controller;
import _11.asktpk.artisanconnectbackend.dto.CategoriesDTO;
import _11.asktpk.artisanconnectbackend.utils.Enums;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/vars")
public class VariablesController {
@GetMapping("/categories")
public List<CategoriesDTO> getAllVariables() {
List<CategoriesDTO> categoriesDTOList = new ArrayList<>();
for (Map.Entry<Enums.Category, String> entry : Enums.categoryPL.entrySet()) {
CategoriesDTO categoriesDTO = new CategoriesDTO();
categoriesDTO.setLabel(entry.getValue());
categoriesDTO.setValue(entry.getKey().toString());
categoriesDTOList.add(categoriesDTO);
}
return categoriesDTOList;
}
@GetMapping("/statuses")
public List<Enums.Status> getAllStatuses() {
return List.of(Enums.Status.values());
}
@GetMapping("/roles")
public List<Enums.Role> getAllRoles() {
return List.of(Enums.Role.values());
}
}

View File

@@ -0,0 +1,59 @@
package _11.asktpk.artisanconnectbackend.controller;
import _11.asktpk.artisanconnectbackend.dto.NoticeDTO;
import _11.asktpk.artisanconnectbackend.dto.RequestResponseDTO;
import _11.asktpk.artisanconnectbackend.dto.WishlistDTO;
import _11.asktpk.artisanconnectbackend.service.ClientService;
import _11.asktpk.artisanconnectbackend.service.NoticeService;
import _11.asktpk.artisanconnectbackend.service.WishlistService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/wishlist")
public class WishlistController {
private final WishlistService wishlistService;
private final ClientService clientService;
private final NoticeService noticeService;
public WishlistController(WishlistService wishlistService, ClientService clientService, NoticeService noticeService) {
this.wishlistService = wishlistService;
this.clientService = clientService;
this.noticeService = noticeService;
}
@PostMapping("/toggle")
public ResponseEntity<RequestResponseDTO> toggleWishlist(@RequestBody WishlistDTO wishlistDTO) {
Long noticeId = wishlistDTO.getNoticeId();
Long clientId = wishlistDTO.getClientId();
NoticeDTO noticeDTO = noticeService.getNoticeById(noticeId);
if (noticeDTO == null) {
return ResponseEntity.badRequest().body(new RequestResponseDTO("Notice not found"));
}
boolean added = wishlistService.toggleWishlist(
clientService.getClientById(clientId),
noticeService.getNoticeByIdEntity(noticeId)
);
if (added) {
return ResponseEntity.ok(new RequestResponseDTO("Wishlist entry added"));
} else {
return ResponseEntity.ok(new RequestResponseDTO("Wishlist entry removed"));
}
}
// @GetMapping("/{clientId}")
// public ResponseEntity<List<WishlistDTO>> getWishlist(@PathVariable Long clientId) {
// List<WishlistDTO> wishlist = wishlistService.getWishlistForClientId(clientId);
// return ResponseEntity.ok(wishlist);
// }
@GetMapping("/")
public List<NoticeDTO> getWishlistForClient() {
// TODO: Replace with actual client ID from authentication context
Long clientId = 1L;
return wishlistService.getNoticesInWishlist(clientId);
}
}

View File

@@ -0,0 +1,16 @@
package _11.asktpk.artisanconnectbackend.dto;
//[
// { "label": "Meble", "value": "Furniture" },
// { "label": "Biżuteria", "value": "Jewelry" },
// { "label": "Ceramika", "value": "Ceramics" }
//]
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class CategoriesDTO {
String label;
String value;
}

View File

@@ -0,0 +1,9 @@
package _11.asktpk.artisanconnectbackend.dto;
import org.springframework.core.io.Resource;
public class ImageRequestDTO {
public Resource image;
public Long noticeId;
public boolean isMainImage;
}

View File

@@ -0,0 +1,19 @@
package _11.asktpk.artisanconnectbackend.dto;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class NoticeAdditionDTO {
public Long noticeId;
public String message;
public NoticeAdditionDTO(String message) {
this.message = message;
}
public NoticeAdditionDTO(Long noticeId, String message) {
this.noticeId = noticeId;
this.message = message;
}
}

View File

@@ -0,0 +1,9 @@
package _11.asktpk.artisanconnectbackend.dto;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class NoticeBoostDTO {
private Long noticeId;
}

View File

@@ -5,7 +5,7 @@ import _11.asktpk.artisanconnectbackend.utils.Enums;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@Getter @Setter
@@ -22,30 +22,15 @@ public class NoticeDTO {
private Enums.Category category;
private List<String> images;
private Enums.Status status;
private LocalDate publishDate;
private LocalDateTime publishDate;
private List<AttributesNotice> attributesNotices;
private boolean isWishlisted;
public NoticeDTO() {
}
public NoticeDTO(Long noticeId, String title, Long clientId, String description, Double price,
Enums.Category category, List<String> images, Enums.Status status,
LocalDate publishDate, List<AttributesNotice> attributesNotices) {
this.noticeId = noticeId;
this.title = title;
this.clientId = clientId;
this.description = description;
this.price = price;
this.category = category;
this.images = images;
this.status = status;
this.publishDate = publishDate;
this.attributesNotices = attributesNotices;
}
}

View File

@@ -0,0 +1,13 @@
package _11.asktpk.artisanconnectbackend.dto;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class RequestResponseDTO {
public String message;
public RequestResponseDTO(String message) {
this.message = message;
}
}

View File

@@ -0,0 +1,12 @@
package _11.asktpk.artisanconnectbackend.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class WishlistDTO {
private Long id;
private Long clientId;
private Long noticeId;
}

View File

@@ -0,0 +1,23 @@
package _11.asktpk.artisanconnectbackend.entities;
import jakarta.persistence.*;
import jdk.jfr.BooleanFlag;
import lombok.Getter;
import lombok.Setter;
@Entity
@Table(name = "images")
@Getter @Setter
public class Image {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long noticeId;
private String imageName;
@BooleanFlag
private boolean isMainImage;
}

View File

@@ -2,7 +2,7 @@ package _11.asktpk.artisanconnectbackend.entities;
import jakarta.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import _11.asktpk.artisanconnectbackend.utils.Enums.*;
@@ -30,13 +30,10 @@ public class Notice {
@Enumerated(EnumType.STRING)
private Category category;
@ElementCollection
private List<String> images;
@Enumerated(EnumType.STRING)
private Status status;
private LocalDate publishDate;
private LocalDateTime publishDate;
@OneToMany(mappedBy = "notice", cascade = CascadeType.ALL)
private List<AttributesNotice> attributesNotices;

View File

@@ -0,0 +1,24 @@
package _11.asktpk.artisanconnectbackend.entities;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
@Entity
@Table(name = "wishlist")
@Getter
@Setter
public class Wishlist {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "client_id", nullable = false)
private Client client;
@ManyToOne
@JoinColumn(name = "notice_id", nullable = false)
private Notice notice;
}

View File

@@ -0,0 +1,14 @@
package _11.asktpk.artisanconnectbackend.repository;
import _11.asktpk.artisanconnectbackend.entities.Image;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ImageRepository extends JpaRepository<Image, Long> {
List<Image> findByNoticeId(Long noticeId);
boolean existsImageByImageNameEqualsIgnoreCase(String imageName);
void deleteByImageNameEquals(String imageName);
}

View File

@@ -5,4 +5,7 @@ import _11.asktpk.artisanconnectbackend.entities.Notice;
import org.springframework.data.jpa.repository.JpaRepository;
public interface NoticeRepository extends JpaRepository<Notice, Long> {
boolean existsByIdNoticeAndClientId(long noticeId, long clientId);
}

View File

@@ -0,0 +1,16 @@
package _11.asktpk.artisanconnectbackend.repository;
import _11.asktpk.artisanconnectbackend.entities.Client;
import _11.asktpk.artisanconnectbackend.entities.Notice;
import _11.asktpk.artisanconnectbackend.entities.Wishlist;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.Optional;
public interface WishlistRepository extends JpaRepository<Wishlist, Long> {
List<Wishlist> findAllByClientId(Long clientId);
Optional<Wishlist> findByClientAndNotice(Client client, Notice notice);
}

View File

@@ -16,7 +16,7 @@ public class ClientService {
this.clientRepository = clientRepository;
}
public ClientDTO toDto(Client client) {
private ClientDTO toDto(Client client) {
ClientDTO dto = new ClientDTO();
dto.setId(client.getId());
@@ -29,7 +29,7 @@ public class ClientService {
return dto;
}
public Client fromDto(ClientDTO dto) {
private Client fromDto(ClientDTO dto) {
Client client = new Client();
client.setId(dto.getId());
@@ -55,6 +55,10 @@ public class ClientService {
return clientRepository.existsById(id);
}
public ClientDTO addClient(ClientDTO clientDTO) {
return toDto(clientRepository.save(fromDto(clientDTO)));
}
public ClientDTO updateClient(long id, ClientDTO clientDTO) {
Client existingClient = clientRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Nie znaleziono ogłoszenia o ID: " + id));

View File

@@ -0,0 +1,93 @@
package _11.asktpk.artisanconnectbackend.service;
import _11.asktpk.artisanconnectbackend.entities.Image;
import _11.asktpk.artisanconnectbackend.repository.ImageRepository;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@Transactional
public class ImageService {
private final ImageRepository imageRepository;
ImageService(ImageRepository imageRepository) {
this.imageRepository = imageRepository;
}
public String saveImageToStorage(String uploadDirectory, MultipartFile imageFile) throws IOException {
String uniqueFileName = UUID.randomUUID() + imageFile.getOriginalFilename();
Path uploadPath = Path.of(uploadDirectory);
Path filePath = uploadPath.resolve(uniqueFileName);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
Files.copy(imageFile.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
return uniqueFileName;
}
public void addImageNameToDB(String filename, Long noticeId) {
Image image = new Image();
image.setImageName(filename);
image.setNoticeId(noticeId);
imageRepository.save(image);
}
public Resource getImage(String imageDirectory, String imageName) throws IOException {
Path filePath = Paths.get(imageDirectory).resolve(imageName);
Resource resource = new UrlResource(filePath.toUri());
if(imageName.isEmpty() || imageDirectory.isEmpty()) {
throw new IOException("Filename or folder is empty. Please check your request and try again.");
}
if (!resource.exists()) {
throw new IOException("File not found");
}
return resource;
}
public void deleteImage(String imageDirectory, String imageName) throws IOException {
Path imagePath = Path.of(imageDirectory, imageName);
deleteImageRecordFromDB(imageName);
if (Files.exists(imagePath)) {
Files.delete(imagePath);
} else {
throw new IOException("File not found");
}
}
public List<String> getImagesList(Long noticeID) throws Exception {
List<Image> images = imageRepository.findByNoticeId(noticeID);
if (images.isEmpty()) {
throw new Exception("There is no images for this notice");
}
return images.stream()
.map(Image::getImageName)
.collect(Collectors.toList());
}
public void deleteImageRecordFromDB(String imageName) {
if(imageRepository.existsImageByImageNameEqualsIgnoreCase(imageName)) {
imageRepository.deleteByImageNameEquals(imageName);
}
}
}

View File

@@ -5,21 +5,26 @@ import _11.asktpk.artisanconnectbackend.entities.Notice;
import _11.asktpk.artisanconnectbackend.repository.ClientRepository;
import _11.asktpk.artisanconnectbackend.repository.NoticeRepository;
import _11.asktpk.artisanconnectbackend.dto.NoticeDTO;
//import _11.asktpk.artisanconnectbackend.service.WishlistService;
import jakarta.persistence.EntityNotFoundException;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
public class NoticeService {
private final NoticeRepository noticeRepository;
private final ClientRepository clientRepository;
private final WishlistService wishlistService;
public NoticeService(NoticeRepository noticeRepository, ClientRepository clientRepository) {
public NoticeService(NoticeRepository noticeRepository, ClientRepository clientRepository, WishlistService wishlistService) {
this.noticeRepository = noticeRepository;
this.clientRepository = clientRepository;
this.wishlistService = wishlistService;
}
public Notice fromDTO(NoticeDTO dto) {
@@ -28,7 +33,6 @@ public class NoticeService {
notice.setDescription(dto.getDescription());
notice.setPrice(dto.getPrice());
notice.setCategory(dto.getCategory());
notice.setImages(dto.getImages());
notice.setStatus(dto.getStatus());
notice.setPublishDate(dto.getPublishDate());
notice.setAttributesNotices(dto.getAttributesNotices());
@@ -40,20 +44,25 @@ public class NoticeService {
return notice;
}
// Metoda do konwersji Notice na DTO
public NoticeDTO toDTO(Notice notice) {
private NoticeDTO toDTO(Notice notice) {
NoticeDTO dto = new NoticeDTO();
// TODO: To be updated using AuthService after implementing authentication.
Optional<Client> client = clientRepository.findById(1L);
boolean isWishlisted = false;
if (client.isPresent()) {
Client c = client.get();
isWishlisted = wishlistService.isWishlisted(c, notice);
}
dto.setNoticeId(notice.getIdNotice());
dto.setTitle(notice.getTitle());
dto.setClientId(notice.getClient().getId());
dto.setDescription(notice.getDescription());
dto.setPrice(notice.getPrice());
dto.setCategory(notice.getCategory());
dto.setImages(notice.getImages());
dto.setStatus(notice.getStatus());
dto.setPublishDate(notice.getPublishDate());
dto.setAttributesNotices(notice.getAttributesNotices());
dto.setWishlisted(isWishlisted);
return dto;
}
@@ -65,42 +74,66 @@ public class NoticeService {
return result;
}
public void addNotice(Notice notice) {
noticeRepository.save(notice);
public NoticeDTO getNoticeById(Long id) {
Notice notice = noticeRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Nie znaleziono ogłoszenia o ID: " + id));
return toDTO(notice);
}
public Notice getNoticeByIdEntity(Long id) {
return noticeRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Nie znaleziono ogłoszenia o ID: " + id));
}
public Long addNotice(NoticeDTO dto) {
return noticeRepository.save(fromDTO(dto)).getIdNotice();
}
public boolean noticeExists(Long id) {
return noticeRepository.existsById(id);
}
public NoticeDTO updateNotice(Long id, NoticeDTO dto) {
Notice existingNotice = noticeRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Nie znaleziono ogłoszenia o ID: " + id));
public NoticeDTO updateNotice(Long id, NoticeDTO dto) {
Notice existingNotice = noticeRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Nie znaleziono ogłoszenia o ID: " + id));
existingNotice.setTitle(dto.getTitle());
existingNotice.setDescription(dto.getDescription());
existingNotice.setPrice(dto.getPrice());
existingNotice.setCategory(dto.getCategory());
existingNotice.setImages(dto.getImages());
existingNotice.setStatus(dto.getStatus());
existingNotice.setAttributesNotices(dto.getAttributesNotices());
existingNotice.setTitle(dto.getTitle());
existingNotice.setDescription(dto.getDescription());
existingNotice.setPrice(dto.getPrice());
existingNotice.setCategory(dto.getCategory());
existingNotice.setStatus(dto.getStatus());
existingNotice.setAttributesNotices(dto.getAttributesNotices());
if (dto.getClientId() != null && !dto.getClientId().equals(existingNotice.getClient().getId())) {
Client client = clientRepository.findById(dto.getClientId())
.orElseThrow(() -> new EntityNotFoundException("Nie znaleziono klienta o ID: " + dto.getClientId()));
existingNotice.setClient(client);
if (dto.getClientId() != null && !dto.getClientId().equals(existingNotice.getClient().getId())) {
Client client = clientRepository.findById(dto.getClientId())
.orElseThrow(() -> new EntityNotFoundException("Nie znaleziono klienta o ID: " + dto.getClientId()));
existingNotice.setClient(client);
}
return toDTO(noticeRepository.save(existingNotice));
}
return toDTO(noticeRepository.save(existingNotice));
}
public void deleteNotice(Long id) {
if (noticeExists(id)) {
noticeRepository.deleteById(id);
} else {
throw new EntityNotFoundException("Nie znaleziono ogłoszenia o ID: " + id);
public void deleteNotice(Long id) {
if (noticeExists(id)) {
noticeRepository.deleteById(id);
} else {
throw new EntityNotFoundException("Nie znaleziono ogłoszenia o ID: " + id);
}
}
public boolean isNoticeOwnedByClient(long noticeId, long clientId) {
return noticeRepository.existsByIdNoticeAndClientId(noticeId, clientId);
}
public void boostNotice(long noticeId) {
Notice notice = noticeRepository.findById(noticeId)
.orElseThrow(() -> new EntityNotFoundException("Ogłoszenie o ID " + noticeId + " nie istnieje."));
notice.setPublishDate(LocalDateTime.now());
noticeRepository.save(notice);
}
}
}

View File

@@ -0,0 +1,72 @@
package _11.asktpk.artisanconnectbackend.service;
import _11.asktpk.artisanconnectbackend.dto.WishlistDTO;
import _11.asktpk.artisanconnectbackend.dto.NoticeDTO;
import _11.asktpk.artisanconnectbackend.entities.Client;
import _11.asktpk.artisanconnectbackend.entities.Notice;
import _11.asktpk.artisanconnectbackend.entities.Wishlist;
import _11.asktpk.artisanconnectbackend.repository.WishlistRepository;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class WishlistService {
private final WishlistRepository wishlistRepository;
private final NoticeService noticeService;
public WishlistService(WishlistRepository wishlistRepository, @Lazy NoticeService noticeService) {
this.wishlistRepository = wishlistRepository;
this.noticeService = noticeService;
}
public List<WishlistDTO> getWishlistForClientId(Long clientId) {
List<Wishlist> wishlistEntities = wishlistRepository.findAllByClientId(clientId);
return wishlistEntities.stream()
.map(this::toDTO)
.toList();
}
public boolean isWishlisted(Client client, Notice notice) {
Optional<Wishlist> existingEntry = wishlistRepository.findByClientAndNotice(client, notice);
return existingEntry.isEmpty();
}
public boolean toggleWishlist(Client client, Notice notice) {
Optional<Wishlist> existingEntry = wishlistRepository.findByClientAndNotice(client, notice);
if (existingEntry.isPresent()) {
wishlistRepository.delete(existingEntry.get());
return false;
} else {
Wishlist wishlist = new Wishlist();
wishlist.setClient(client);
wishlist.setNotice(notice);
wishlistRepository.save(wishlist);
return true;
}
}
private WishlistDTO toDTO(Wishlist wishlist) {
WishlistDTO dto = new WishlistDTO();
dto.setId(wishlist.getId());
dto.setClientId(wishlist.getClient().getId());
dto.setNoticeId(wishlist.getNotice().getIdNotice());
return dto;
}
public List<NoticeDTO> getNoticesInWishlist(Long clientId) {
List<Wishlist> wishlistEntries = wishlistRepository.findAllByClientId(clientId);
return wishlistEntries.stream()
.map(wishlist -> noticeService.getNoticeById(wishlist.getNotice().getIdNotice()))
.collect(Collectors.toList());
}
}

View File

@@ -1,15 +1,46 @@
package _11.asktpk.artisanconnectbackend.utils;
import java.util.Map;
public class Enums {
public enum Role {
ADMIN, USER
}
public enum Category {
Electronics, Artwork, Kitchen, Buildings, Home, Fashion // Replace with actual categories
Handmade, Woodworking, Metalworking, Ceramics,
Textiles, Jewelry, Leatherwork, Painting, Sculpture,
Glasswork, Furniture, Restoration, Tailoring, Weaving,
Calligraphy, Pottery, Blacksmithing, Basketry, Embroidery,
Knitting, Carpentry, Other
}
public static final Map<Category, String> categoryPL = Map.ofEntries(
Map.entry(Category.Handmade, "Rękodzieło"),
Map.entry(Category.Woodworking, "Stolarstwo"),
Map.entry(Category.Metalworking, "Obróbka metalu"),
Map.entry(Category.Ceramics, "Ceramika"),
Map.entry(Category.Textiles, "Tekstylia"),
Map.entry(Category.Jewelry, "Biżuteria"),
Map.entry(Category.Leatherwork, "Wyroby skórzane"),
Map.entry(Category.Painting, "Malarstwo"),
Map.entry(Category.Sculpture, "Rzeźbiarstwo"),
Map.entry(Category.Glasswork, "Szklarstwo"),
Map.entry(Category.Furniture, "Meble"),
Map.entry(Category.Restoration, "Renowacja"),
Map.entry(Category.Tailoring, "Krawiectwo"),
Map.entry(Category.Weaving, "Tkactwo"),
Map.entry(Category.Calligraphy, "Kaligrafia"),
Map.entry(Category.Pottery, "Garncarstwo"),
Map.entry(Category.Blacksmithing, "Kowalstwo"),
Map.entry(Category.Basketry, "Koszykarstwo"),
Map.entry(Category.Embroidery, "Hafciarstwo"),
Map.entry(Category.Knitting, "Dzierganie"),
Map.entry(Category.Carpentry, "Ciesielstwo"),
Map.entry(Category.Other, "Inne")
);
public enum Status {
ACTIVE, INACTIVE // Replace with actual statuses
ACTIVE, INACTIVE
}
}

View File

@@ -1,13 +1,19 @@
spring.application.name=ArtisanConnectBackend
## PostgreSQL
spring.datasource.url=jdbc:postgresql://192.168.56.103:5432/default_db
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=postgres
spring.datasource.password=postgres
#initial data for db injection
spring.sql.init.data-locations=classpath:sql/data.sql
spring.sql.init.mode=always
spring.jpa.defer-datasource-initialization=true
# create and drop table, good for testing, production set to none or comment it
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.hibernate.ddl-auto=create-drop
file.upload-dir=/Users/andsol/Desktop/uploads
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

View File

@@ -0,0 +1,20 @@
spring.application.name=ArtisanConnectBackend
## PostgreSQL
spring.datasource.url=jdbc:postgresql://db:5432/postgres
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=postgres
spring.datasource.password=postgres
#initial data for db injection
spring.sql.init.data-locations=classpath:sql/data.sql
spring.sql.init.mode=always
spring.jpa.defer-datasource-initialization=true
# create and drop table, good for testing, production set to none or comment it
spring.jpa.hibernate.ddl-auto=update
#file.upload-dir=/Users/andsol/Desktop/uploads
file.upload-dir=/app/images
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

View File

@@ -5,3 +5,11 @@ VALUES
('jane.smith@example.com', 'Jane', 'null', 'Smith', 'securepass', 'USER'),
('michael.brown@example.com', 'Michael', 'null', 'Brown', 'mypassword', 'USER'),
('emily.jones@example.com', 'Emily', 'null', 'Jones', 'passw0rd', 'USER');
INSERT INTO notice (title, description, client_id, price, category, status, publish_date) VALUES
('Ręcznie robiona biżuteria', 'Unikalna biżuteria wykonana ręcznie z najwyższej jakości materiałów.', 5, 150.00, 'Jewelry', 'ACTIVE', '2023-10-01'),
('Drewniany stół', 'Solidny stół wykonany z litego drewna dębowego.', 3, 1200.00, 'Furniture', 'ACTIVE', '2023-09-15'),
('Ceramiczna waza', 'Piękna waza ceramiczna, idealna na prezent.', 2, 300.00, 'Ceramics', 'INACTIVE', '2023-08-20'),
('Obraz olejny', 'Obraz olejny przedstawiający krajobraz górski.', 4, 800.00, 'Painting', 'ACTIVE', '2023-07-10'),
('Skórzany portfel', 'Ręcznie wykonany portfel ze skóry naturalnej.', 1, 250.00, 'Leatherwork', 'ACTIVE', '2023-06-05');

View File

@@ -0,0 +1,122 @@
<mxfile host="Electron" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/26.2.2 Chrome/134.0.6998.178 Electron/35.1.2 Safari/537.36" version="26.2.2">
<diagram name="Strona-1" id="y3JY7GFactLq4jGyxg2V">
<mxGraphModel dx="2180" dy="884" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="Ht36qwYIb0A0iwGBjog0-3" value="&lt;font style=&quot;font-size: 15px;&quot;&gt;&lt;b&gt;Backend&lt;/b&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#f5f5f5;fillStyle=auto;strokeColor=#666666;shadow=0;glass=0;fontColor=#333333;" vertex="1" parent="1">
<mxGeometry x="270" y="520" width="534" height="270" as="geometry" />
</mxCell>
<mxCell id="HPrCYg6BNmEDSauX9FRR-4" value="&lt;font style=&quot;font-size: 15px;&quot;&gt;&lt;b&gt;Backend&lt;/b&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#f5f5f5;fillStyle=auto;strokeColor=#666666;shadow=0;glass=0;fontColor=#333333;" parent="1" vertex="1">
<mxGeometry x="324" y="100" width="470" height="230" as="geometry" />
</mxCell>
<mxCell id="HPrCYg6BNmEDSauX9FRR-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="HPrCYg6BNmEDSauX9FRR-2" target="HPrCYg6BNmEDSauX9FRR-7" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="HPrCYg6BNmEDSauX9FRR-2" value="Postman" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="34" y="250" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="HPrCYg6BNmEDSauX9FRR-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="HPrCYg6BNmEDSauX9FRR-7" target="HPrCYg6BNmEDSauX9FRR-10" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="HPrCYg6BNmEDSauX9FRR-7" value="Controller" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="364" y="250" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="HPrCYg6BNmEDSauX9FRR-13" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="HPrCYg6BNmEDSauX9FRR-10" target="HPrCYg6BNmEDSauX9FRR-11" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="HPrCYg6BNmEDSauX9FRR-10" value="Service" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="554" y="250" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="HPrCYg6BNmEDSauX9FRR-11" value="Repository" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="664" y="130" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="HPrCYg6BNmEDSauX9FRR-17" value="DTO" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="504" y="270" width="30" height="20" as="geometry" />
</mxCell>
<mxCell id="HPrCYg6BNmEDSauX9FRR-19" value="Entity" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="704" y="230" width="40" height="20" as="geometry" />
</mxCell>
<mxCell id="HPrCYg6BNmEDSauX9FRR-1" value="DTO" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="224" y="270" width="40" height="20" as="geometry" />
</mxCell>
<mxCell id="HPrCYg6BNmEDSauX9FRR-44" value="Przesyłanie danych na serwer" style="text;strokeColor=none;fillColor=none;html=1;fontSize=24;fontStyle=1;verticalAlign=middle;align=center;" parent="1" vertex="1">
<mxGeometry x="226" y="40" width="376" height="40" as="geometry" />
</mxCell>
<mxCell id="HPrCYg6BNmEDSauX9FRR-50" value="" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=none;fillColor=#DF8C42;labelPosition=center;verticalLabelPosition=bottom;verticalAlign=top;align=center;outlineConnect=0;shape=mxgraph.veeam2.restful_api;" parent="1" vertex="1">
<mxGeometry x="10" y="10" width="80" height="80" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-1" value="Przesyłanie zdjęć na backend" style="text;strokeColor=none;fillColor=none;html=1;fontSize=24;fontStyle=1;verticalAlign=middle;align=center;" vertex="1" parent="1">
<mxGeometry x="194" y="460" width="440" height="40" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="Ht36qwYIb0A0iwGBjog0-2" target="Ht36qwYIb0A0iwGBjog0-4">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-2" value="Postman" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="-20" y="625" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="Ht36qwYIb0A0iwGBjog0-4" target="Ht36qwYIb0A0iwGBjog0-5">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-4" value="ImageController&lt;div&gt;(Validation)&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="307" y="625" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-5" value="ImageService" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;align=center;" vertex="1" parent="1">
<mxGeometry x="550" y="580" width="196" height="150" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-9" value="MultipartFile + PathVariable" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="140" y="640" width="110" height="30" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-17" value="saveImageToStorage()" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="580" y="620" width="136" height="30" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-18" value="addImageNameToDB()" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="582" y="680" width="132" height="30" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-20" value="Pobieranie zdjęć z backendu" style="text;strokeColor=none;fillColor=none;html=1;fontSize=24;fontStyle=1;verticalAlign=middle;align=center;" vertex="1" parent="1">
<mxGeometry x="194" y="1010" width="440" height="40" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-22" value="&lt;font style=&quot;font-size: 15px;&quot;&gt;&lt;b&gt;Backend&lt;/b&gt;&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;fillColor=#f5f5f5;fillStyle=auto;strokeColor=#666666;shadow=0;glass=0;fontColor=#333333;" vertex="1" parent="1">
<mxGeometry x="260" y="1079" width="534" height="290" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-29" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.75;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;" edge="1" parent="1" source="Ht36qwYIb0A0iwGBjog0-23" target="Ht36qwYIb0A0iwGBjog0-26">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-30" value="GetRequest" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="Ht36qwYIb0A0iwGBjog0-29">
<mxGeometry x="0.2401" relative="1" as="geometry">
<mxPoint x="-19" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-23" value="Postman" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="-110" y="1197" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-24" value="/api/v1/images/get/{filename}" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="-135" y="1160" width="170" height="30" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-35" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.25;exitDx=0;exitDy=0;entryX=1;entryY=0.25;entryDx=0;entryDy=0;" edge="1" parent="1" source="Ht36qwYIb0A0iwGBjog0-26" target="Ht36qwYIb0A0iwGBjog0-23">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-36" value="MultipartFile(content-type image/jpeg)" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="Ht36qwYIb0A0iwGBjog0-35">
<mxGeometry x="0.0779" y="-1" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-26" value="ImageController" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="294" y="1197" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-37" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="Ht36qwYIb0A0iwGBjog0-31" target="Ht36qwYIb0A0iwGBjog0-26">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-31" value="ImageService" style="rounded=0;whiteSpace=wrap;html=1;verticalAlign=top;align=center;" vertex="1" parent="1">
<mxGeometry x="534" y="1152" width="196" height="150" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-32" value="getImage()" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="564" y="1209" width="136" height="30" as="geometry" />
</mxCell>
<mxCell id="Ht36qwYIb0A0iwGBjog0-38" value="/api/v1/images/upload/{noticeID}" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="-60" y="590" width="200" height="30" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>