15 Commits

Author SHA1 Message Date
211d9b5974 чето не то 2025-10-22 18:36:47 +04:00
b1cdb05630 Готовая лаба2 и отчет 2025-10-09 13:21:28 +04:00
2aeb01eaca Доделал чето 2025-10-08 15:25:07 +04:00
af411710b8 Create PostServiceTest.java 2025-09-19 20:21:00 +04:00
ff3bc0b9ef Create PostService.java 2025-09-19 20:20:58 +04:00
27a4dca41d Create PostRepository.java 2025-09-19 20:20:57 +04:00
4d5ce8b8b7 Create MapRepository.java 2025-09-19 20:20:55 +04:00
f5d0752036 Create CommonRepository.java 2025-09-19 20:20:53 +04:00
8233f29056 Update Post.java 2025-09-19 20:20:51 +04:00
dc5b47be73 Create PostRsDto.java 2025-09-19 20:20:50 +04:00
53f53d43c2 Create BaseEntity.java 2025-09-19 20:20:48 +04:00
077f334ebb Create PostRqDto.java 2025-09-19 20:20:45 +04:00
8aacaa0126 Update PostMapper.java 2025-09-19 20:20:42 +04:00
0d5ca036e5 Create PostController.java 2025-09-19 20:20:40 +04:00
3b831c8485 111 2025-09-19 20:20:26 +04:00
16 changed files with 367 additions and 316 deletions

View File

@@ -27,6 +27,9 @@ dependencies {
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
}
tasks.named('test') {

View File

@@ -4,13 +4,14 @@ import java.time.LocalDateTime;
import org.springframework.stereotype.Component;
import io.github.f1rstteam.ip_backend_labs.DTO.PostDTO;
import io.github.f1rstteam.ip_backend_labs.DTO.PostRqDto;
import io.github.f1rstteam.ip_backend_labs.DTO.PostRsDto;
import io.github.f1rstteam.ip_backend_labs.model.Post;
@Component
public class PostMapper {
public PostDTO toDTO(Post post) {
PostDTO dto = new PostDTO();
public static PostRsDto toDto(Post post) {
PostRsDto dto = new PostRsDto();
dto.setId(post.getId());
dto.setText(post.getText());
dto.setImage(post.getImage());
@@ -22,16 +23,15 @@ public class PostMapper {
return dto;
}
public Post toEntity(PostDTO postDTO) {
public static Post toEntity(PostRqDto dto) {
Post post = new Post();
post.setId(postDTO.getId() != null ? postDTO.getId() : null);
post.setText(postDTO.getText() != null ? postDTO.getText() : "");
post.setImage(postDTO.getImage() != null ? postDTO.getImage() : "");
post.setGroup(postDTO.getGroup() != null ? postDTO.getGroup() : "Моя группа");
post.setLikes(postDTO.getLikes() >= 0 ? postDTO.getLikes() : 0);
post.setComments(postDTO.getComments() >= 0 ? postDTO.getComments() : 0);
post.setShares(postDTO.getShares() >= 0 ? postDTO.getShares() : 0);
post.setCreatedAt(postDTO.getCreatedAt() != null ? postDTO.getCreatedAt() : LocalDateTime.now());
post.setText(dto.getText() != null ? dto.getText() : "");
post.setImage(dto.getImage() != null ? dto.getImage() : "");
post.setGroup(dto.getGroup() != null ? dto.getGroup() : "Моя группа");
post.setLikes(0);
post.setComments(0);
post.setShares(0);
post.setCreatedAt(LocalDateTime.now());
return post;
}
}

View File

@@ -0,0 +1,24 @@
package io.github.f1rstteam.ip_backend_labs.DTO;
public class PostRqDto {
private String text;
private String image;
private String group;
public PostRqDto() {}
public PostRqDto(String text, String image, String group) {
this.text = text;
this.image = image;
this.group = group;
}
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public String getImage() { return image; }
public void setImage(String image) { this.image = image; }
public String getGroup() { return group; }
public void setGroup(String group) { this.group = group; }
}

View File

@@ -2,8 +2,8 @@ package io.github.f1rstteam.ip_backend_labs.DTO;
import java.time.LocalDateTime;
public class PostDTO {
private Long id;
public class PostRsDto {
private Long id;
private String text;
private String image;
private String group;
@@ -11,10 +11,10 @@ public class PostDTO {
private int comments;
private int shares;
private LocalDateTime createdAt;
public PostDTO() {}
public PostDTO(Long id, String text, String image, String group, int likes, int comments, int shares, LocalDateTime createdAt) {
public PostRsDto() {}
public PostRsDto(Long id, String text, String image, String group, int likes, int comments, int shares, LocalDateTime createdAt) {
this.id = id;
this.text = text;
this.image = image;
@@ -26,20 +26,26 @@ public class PostDTO {
}
public Long getId() { return id; }
public String getText() { return text; }
public String getImage() { return image; }
public String getGroup() { return group; }
public int getLikes() { return likes; }
public int getComments() { return comments; }
public int getShares() { return shares; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setId(Long id) { this.id = id; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public String getImage() { return image; }
public void setImage(String image) { this.image = image; }
public String getGroup() { return group; }
public void setGroup(String group) { this.group = group; }
public int getLikes() { return likes; }
public void setLikes(int likes) { this.likes = likes; }
public int getComments() { return comments; }
public void setComments(int comments) { this.comments = comments; }
public int getShares() { return shares; }
public void setShares(int shares) { this.shares = shares; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
}

View File

@@ -0,0 +1,11 @@
package io.github.f1rstteam.ip_backend_labs.Repositories;
import java.util.List;
import java.util.Optional;
public interface CommonRepository<T, ID> {
T save(ID id, T entity);
Optional<T> findById(ID id);
List<T> findAll();
void deleteById(ID id);
}

View File

@@ -0,0 +1,28 @@
package io.github.f1rstteam.ip_backend_labs.Repositories;
import java.util.*;
public abstract class MapRepository<T, ID> implements CommonRepository<T, ID> {
protected final Map<ID, T> storage = new HashMap<>();
@Override
public T save(ID id, T entity) {
storage.put(id, entity);
return entity;
}
@Override
public Optional<T> findById(ID id) {
return Optional.ofNullable(storage.get(id));
}
@Override
public List<T> findAll() {
return new ArrayList<>(storage.values());
}
@Override
public void deleteById(ID id) {
storage.remove(id);
}
}

View File

@@ -0,0 +1,9 @@
package io.github.f1rstteam.ip_backend_labs.Repositories;
import org.springframework.stereotype.Repository;
import io.github.f1rstteam.ip_backend_labs.model.Post;
@Repository
public class PostRepository extends MapRepository<Post, Long> implements CommonRepository<Post, Long> {
}

View File

@@ -1,146 +0,0 @@
package io.github.f1rstteam.ip_backend_labs.Service;
import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.github.f1rstteam.ip_backend_labs.model.Post;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
@Service
public class JsonPostService {
private static final String STORAGE_FILE = "posts.json";
private final ObjectMapper objectMapper = new ObjectMapper();
private List<Post> posts = new ArrayList<>();
private final AtomicLong counter = new AtomicLong(1);
public JsonPostService() {
objectMapper.registerModule(new JavaTimeModule());
}
@PostConstruct
public void loadPosts() {
try {
File file = new File(STORAGE_FILE);
if (file.exists()) {
CollectionType listType = objectMapper.getTypeFactory()
.constructCollectionType(List.class, Post.class);
posts = objectMapper.readValue(file, listType);
System.out.println("Загружено " + posts.size() + " постов из файла");
if (!posts.isEmpty()) {
long maxId = posts.stream()
.mapToLong(Post::getId)
.max()
.orElse(0);
counter.set(maxId + 1);
}
} else {
posts = getInitialPosts();
savePostsImmediately();
System.out.println("Созданы начальные посты: " + posts.size());
}
} catch (IOException e) {
System.out.println("Ошибка загрузки файла: " + e.getMessage());
posts = getInitialPosts();
}
}
@PreDestroy
public void savePosts() {
try {
objectMapper.writeValue(new File(STORAGE_FILE), posts);
System.out.println("Сохранено " + posts.size() + " постов в файл");
} catch (IOException e) {
System.out.println("Ошибка сохранения файла: " + e.getMessage());
}
}
public void savePostsImmediately() {
savePosts();
}
private List<Post> getInitialPosts() {
List<Post> initialPosts = new ArrayList<>();
LocalDateTime now = LocalDateTime.now();
initialPosts.add(new Post(
1L,
"Первый мем",
"/images/meme1.jpg",
"Какая-то группа 1",
8, 2, 1,
now.minusDays(2)
));
initialPosts.add(new Post(
2L,
"Второй мем",
"/images/meme2.jpg",
"Какая-то группа 2",
785, 102, 14,
now.minusDays(2)
));
return initialPosts;
}
public List<Post> getAllPosts() {
return new ArrayList<>(posts);
}
public Post getPostById(Long id) {
return posts.stream()
.filter(post -> post.getId().equals(id))
.findFirst()
.orElseThrow(() -> new RuntimeException("Post not found"));
}
public Post createPost(Post post) {
post.setId(counter.getAndIncrement());
post.setCreatedAt(LocalDateTime.now());
posts.add(post);
savePostsImmediately();
return post;
}
public Post updatePost(Long id, Post updatedPost) {
Post post = getPostById(id);
post.setText(updatedPost.getText());
post.setImage(updatedPost.getImage());
post.setGroup(updatedPost.getGroup());
post.setLikes(updatedPost.getLikes());
post.setComments(updatedPost.getComments());
post.setShares(updatedPost.getShares());
savePostsImmediately();
return post;
}
public void deletePost(Long id) {
Post post = getPostById(id);
posts.remove(post);
savePostsImmediately();
}
public Post likePost(Long id) {
Post post = getPostById(id);
post.setLikes(post.getLikes() + 1);
savePostsImmediately();
return post;
}
}

View File

@@ -0,0 +1,68 @@
package io.github.f1rstteam.ip_backend_labs.Service;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import io.github.f1rstteam.ip_backend_labs.DTO.PostRqDto;
import io.github.f1rstteam.ip_backend_labs.DTO.PostRsDto;
import io.github.f1rstteam.ip_backend_labs.DTO.Mapping.PostMapper;
import io.github.f1rstteam.ip_backend_labs.Repositories.PostRepository;
import io.github.f1rstteam.ip_backend_labs.model.Post;
@Service
public class PostService {
private final PostRepository repository;
private final AtomicLong idGenerator = new AtomicLong(1);
public PostService(PostRepository repository) {
this.repository = repository;
}
// CREATE
public PostRsDto create(PostRqDto rqDto) {
Post post = PostMapper.toEntity(rqDto);
Long id = idGenerator.getAndIncrement();
post.setId(id);
repository.save(id, post);
return PostMapper.toDto(post);
}
// READ by ID
public PostRsDto getById(Long id) {
return repository.findById(id)
.map(PostMapper::toDto)
.orElseThrow(() -> new RuntimeException("Post not found with id " + id));
}
// READ all
public List<PostRsDto> getAll() {
return repository.findAll()
.stream()
.map(PostMapper::toDto)
.collect(Collectors.toList());
}
// UPDATE
public PostRsDto update(Long id, PostRqDto rqDto) {
Post existing = repository.findById(id)
.orElseThrow(() -> new RuntimeException("Post not found with id " + id));
existing.setText(rqDto.getText());
existing.setImage(rqDto.getImage());
existing.setGroup(rqDto.getGroup());
repository.save(id, existing);
return PostMapper.toDto(existing);
}
// DELETE
public void delete(Long id) {
if (repository.findById(id).isEmpty()) {
throw new RuntimeException("Post not found with id " + id);
}
repository.deleteById(id);
}
}

View File

@@ -0,0 +1,66 @@
package io.github.f1rstteam.ip_backend_labs.controllers;
import java.util.List;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.github.f1rstteam.ip_backend_labs.DTO.PostRqDto;
import io.github.f1rstteam.ip_backend_labs.DTO.PostRsDto;
import io.github.f1rstteam.ip_backend_labs.Service.PostService;
@RestController
@RequestMapping("/api/posts")
public class PostController {
private final PostService service;
public PostController(PostService service) {
this.service = service;
}
// CREATE
@PostMapping
public PostRsDto create(@RequestBody PostRqDto rqDto) {
validate(rqDto);
return service.create(rqDto);
}
// READ by ID
@GetMapping("/{id}")
public PostRsDto getById(@PathVariable Long id) {
return service.getById(id);
}
// READ all
@GetMapping
public List<PostRsDto> getAll() {
return service.getAll();
}
// UPDATE
@PutMapping("/{id}")
public PostRsDto update(@PathVariable Long id, @RequestBody PostRqDto rqDto) {
validate(rqDto);
return service.update(id, rqDto);
}
// DELETE
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
service.delete(id);
}
// Валидация
private void validate(PostRqDto dto) {
if (dto.getText() == null || dto.getText().isBlank()) {
throw new IllegalArgumentException("Post text cannot be empty");
}
}
}

View File

@@ -1,133 +0,0 @@
package io.github.f1rstteam.ip_backend_labs.controllers;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.github.f1rstteam.ip_backend_labs.DTO.PostDTO;
import io.github.f1rstteam.ip_backend_labs.DTO.Mapping.PostMapper;
import io.github.f1rstteam.ip_backend_labs.Service.JsonPostService;
import io.github.f1rstteam.ip_backend_labs.model.Post;
@RestController
@RequestMapping("/api/posts")
public class PostControllerDTO {
private final JsonPostService postService;
private final PostMapper postMapper;
private final List<PostDTO> postsDTO = new ArrayList<>();
public PostControllerDTO(JsonPostService postService, PostMapper postMapper) {
this.postService = postService;
this.postMapper = postMapper;
initializePredefinedData();
}
private void initializePredefinedData() {
postsDTO.addAll(postService.getAllPosts().stream()
.map(postMapper::toDTO)
.collect(Collectors.toList()));
}
@GetMapping
public List<PostDTO> getAllPosts() {
return postService.getAllPosts().stream()
.map(postMapper::toDTO)
.collect(Collectors.toList());
}
@GetMapping("/{postId}")
public PostDTO getPostById(@PathVariable("postId") Long id) {
return postMapper.toDTO(postService.getPostById(id));
}
@PostMapping
public PostDTO createPost(@RequestBody PostDTO postDTO) {
Post post = postMapper.toEntity(postDTO);
Post savedPost = postService.createPost(post);
return postMapper.toDTO(savedPost);
}
@PutMapping("/{postId}")
public PostDTO updatePost(@PathVariable("postId") Long id, @RequestBody PostDTO postDTO) {
Post existingPost = postService.getPostById(id);
existingPost.setText(postDTO.getText());
existingPost.setImage(postDTO.getImage());
existingPost.setGroup(postDTO.getGroup());
existingPost.setLikes(postDTO.getLikes());
existingPost.setComments(postDTO.getComments());
existingPost.setShares(postDTO.getShares());
Post updatedPost = postService.updatePost(id, existingPost);
return postMapper.toDTO(updatedPost);
}
@DeleteMapping("/{postId}")
public void deletePost(@PathVariable("postId") Long id) {
postService.deletePost(id);
}
@PatchMapping("/{postId}/like")
public PostDTO likePost(@PathVariable("postId") Long id) {
Post existingPost = postService.getPostById(id);
existingPost.setLikes(existingPost.getLikes() + 1);
Post updatedPost = postService.updatePost(id, existingPost);
return postMapper.toDTO(updatedPost);
}
@PatchMapping("/{postId}/dislike")
public PostDTO dislikePost(@PathVariable("postId") Long id) {
Post existingPost = postService.getPostById(id);
existingPost.setLikes(Math.max(0, existingPost.getLikes() - 1));
Post updatedPost = postService.updatePost(id, existingPost);
return postMapper.toDTO(updatedPost);
}
@PatchMapping("/{postId}/comment")
public PostDTO commentPost(@PathVariable("postId") Long id) {
Post existingPost = postService.getPostById(id);
existingPost.setComments(existingPost.getComments() + 1);
Post updatedPost = postService.updatePost(id, existingPost);
return postMapper.toDTO(updatedPost);
}
@PatchMapping("/{postId}/share")
public PostDTO sharePost(@PathVariable("postId") Long id) {
Post existingPost = postService.getPostById(id);
existingPost.setShares(existingPost.getShares() + 1);
Post updatedPost = postService.updatePost(id, existingPost);
return postMapper.toDTO(updatedPost);
}
@PatchMapping("/{postId}/unshare")
public PostDTO unsharePost(@PathVariable("postId") Long id) {
Post existingPost = postService.getPostById(id);
existingPost.setShares(Math.max(0, existingPost.getShares() - 1));
Post updatedPost = postService.updatePost(id, existingPost);
return postMapper.toDTO(updatedPost);
}
}

View File

@@ -0,0 +1,30 @@
package io.github.f1rstteam.ip_backend_labs.model;
import java.time.LocalDateTime;
public abstract class BaseEntity {
protected Long id;
protected LocalDateTime createdAt;
public BaseEntity() {
}
public BaseEntity(Long id, LocalDateTime createdAt) {
this.id = id;
this.createdAt = createdAt;
}
public Long getId() {
return id;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setId(Long id) {
this.id = id;
}
public void setCreatedAt(LocalDateTime createdAt){
this.createdAt = createdAt;
}
}

View File

@@ -2,34 +2,28 @@ package io.github.f1rstteam.ip_backend_labs.model;
import java.time.LocalDateTime;
public class Post {
private Long id;
public class Post extends BaseEntity {
private String text;
private String image;
private String group;
private int likes;
private int comments;
private int shares;
private LocalDateTime createdAt;
public Post() {}
public Post(Long id, String text, String image, String group, int likes, int comments, int shares, LocalDateTime createdAt) {
this.id = id;
super(id, createdAt);
this.text = text;
this.image = image;
this.group = group;
this.likes = likes;
this.comments = comments;
this.shares = shares;
this.createdAt = createdAt;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
@@ -47,7 +41,4 @@ public class Post {
public int getShares() { return shares; }
public void setShares(int shares) { this.shares = shares; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
}

View File

@@ -3,3 +3,13 @@ spring.application.name=Internet Programming Backend Labs
# Available levels: TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
logging.level.ru.ulstu.is.server=DEBUG
spring.datasource.url=jdbc:h2:file:./data
spring.datasource.username=sa
spring.datasource.password=sa
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create
# spring.jpa.show-sql=true
# spring.jpa.properties.hibernate.format_sql=true
spring.h2.console.enabled=true

View File

@@ -0,0 +1,84 @@
package io.github.f1rstteam.ip_backend_labs;
import org.junit.jupiter.api.BeforeEach;
import io.github.f1rstteam.ip_backend_labs.DTO.PostRqDto;
import io.github.f1rstteam.ip_backend_labs.DTO.PostRsDto;
import io.github.f1rstteam.ip_backend_labs.Repositories.PostRepository;
import io.github.f1rstteam.ip_backend_labs.Service.PostService;
import io.github.f1rstteam.ip_backend_labs.model.Post;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.time.LocalDateTime;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.*;
class PostServiceTest {
private PostRepository postRepository;
private PostService postService;
@BeforeEach
void setUp() {
postRepository = mock(PostRepository.class);
postService = new PostService(postRepository);
}
@Test
void testCreate() {
PostRqDto rq = new PostRqDto("Hello", "image.png", "group1");
PostRsDto result = postService.create(rq);
assertNotNull(result);
assertEquals("Hello", result.getText());
ArgumentCaptor<Post> postCaptor = ArgumentCaptor.forClass(Post.class);
verify(postRepository).save(anyLong(), postCaptor.capture());
assertEquals("Hello", postCaptor.getValue().getText());
}
@Test
void testGetById_found() {
Post post = new Post(1L, "Test", "img.png", "group", 0, 0, 0, LocalDateTime.now());
when(postRepository.findById(1L)).thenReturn(Optional.of(post));
PostRsDto result = postService.getById(1L);
assertNotNull(result);
assertEquals("Test", result.getText());
}
@Test
void testGetById_notFound() {
when(postRepository.findById(99L)).thenReturn(Optional.empty());
assertThrows(RuntimeException.class, () -> postService.getById(99L));
}
@Test
void testUpdate() {
Post existing = new Post(1L, "Old", "old.png", "group", 0, 0, 0, LocalDateTime.now());
when(postRepository.findById(1L)).thenReturn(Optional.of(existing));
PostRqDto rq = new PostRqDto("New text", "new.png", "group");
PostRsDto result = postService.update(1L, rq);
assertEquals("New text", result.getText());
assertEquals("new.png", result.getImage());
}
@Test
void testDelete() {
Post post = new Post(1L, "Test", "img.png", "group", 0, 0, 0, LocalDateTime.now());
when(postRepository.findById(1L)).thenReturn(Optional.of(post));
postService.delete(1L);
verify(postRepository).deleteById(1L);
}
}

BIN
Отчет_2.docx Normal file

Binary file not shown.