Skip to content

Commit e71dfbf

Browse files
authored
Merge pull request #115 from doctorixx/master
Add posts
2 parents d521a9d + 3e3b5e3 commit e71dfbf

19 files changed

Lines changed: 578 additions & 2 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package ru.codebattles.backend.dto
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
import jakarta.validation.constraints.NotBlank
5+
import jakarta.validation.constraints.NotNull
6+
7+
data class CreatePostDto(
8+
@field:NotBlank
9+
@Schema(description = "Title of the post", example = "Welcome to Code Battles")
10+
val title: String,
11+
12+
@field:NotBlank
13+
@Schema(description = "Content of the post", example = "This is the main content of the post...")
14+
val content: String,
15+
16+
@field:NotNull
17+
@Schema(description = "Show post at main page", example = "true")
18+
val showAtMain: Boolean,
19+
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package ru.codebattles.backend.dto
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
5+
data class PostDto(
6+
@Schema(description = "Unique identifier of the post", example = "1")
7+
val id: Long? = null,
8+
9+
10+
@Schema(description = "Title of post")
11+
val title: String,
12+
13+
@Schema(description = "Content of post")
14+
val content: String,
15+
16+
@Schema(description = "Show post at main page")
17+
val showAtMain: Boolean,
18+
)
19+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package ru.codebattles.backend.dto.mapper
2+
3+
import org.mapstruct.Mapper
4+
import ru.codebattles.backend.dto.CreatePostDto
5+
import ru.codebattles.backend.dto.mapper.core.AbstractMapper
6+
import ru.codebattles.backend.entity.Posts
7+
8+
@Mapper(componentModel = "spring")
9+
interface CreatePostMapper : AbstractMapper<Posts, CreatePostDto>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package ru.codebattles.backend.dto.mapper
2+
3+
import org.mapstruct.Mapper
4+
import ru.codebattles.backend.dto.PostDto
5+
import ru.codebattles.backend.dto.mapper.core.AbstractMapper
6+
import ru.codebattles.backend.entity.Posts
7+
8+
@Mapper(componentModel = "spring")
9+
interface PostMapper : AbstractMapper<Posts, PostDto>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package ru.codebattles.backend.entity
2+
3+
import jakarta.persistence.Column
4+
import jakarta.persistence.Entity
5+
import jakarta.persistence.Table
6+
7+
@Entity
8+
@Table(name = "posts")
9+
data class Posts(
10+
@Column(nullable = false)
11+
var title: String,
12+
13+
@Column(nullable = false)
14+
var content: String,
15+
16+
@Column(nullable = false)
17+
var showAtMain: Boolean,
18+
) : BaseEntity()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package ru.codebattles.backend.repository
2+
3+
import org.springframework.data.jpa.repository.JpaRepository
4+
import ru.codebattles.backend.entity.Posts
5+
6+
interface PostRepository : JpaRepository<Posts, Long> {
7+
fun findByShowAtMainTrue(): List<Posts>
8+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package ru.codebattles.backend.services
2+
3+
import org.springframework.http.HttpStatus
4+
import org.springframework.stereotype.Service
5+
import org.springframework.web.server.ResponseStatusException
6+
import ru.codebattles.backend.dto.CreatePostDto
7+
import ru.codebattles.backend.dto.PostDto
8+
import ru.codebattles.backend.dto.mapper.PostMapper
9+
import ru.codebattles.backend.entity.Posts
10+
import ru.codebattles.backend.repository.PostRepository
11+
12+
@Service
13+
class PostService(
14+
private val postRepository: PostRepository,
15+
private val postMapper: PostMapper,
16+
) {
17+
18+
fun getAll(): List<PostDto> {
19+
return postMapper.toDtoS(
20+
postRepository.findAll()
21+
)
22+
}
23+
24+
fun getById(id: Long): PostDto {
25+
val optionalPost = postRepository.findById(id)
26+
if (optionalPost.isPresent) {
27+
return postMapper.toDto(optionalPost.get())
28+
}
29+
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Post not found")
30+
}
31+
32+
fun getMainPagePosts(): List<PostDto> {
33+
return postMapper.toDtoS(
34+
postRepository.findByShowAtMainTrue()
35+
)
36+
}
37+
38+
fun create(createPostDto: CreatePostDto): PostDto {
39+
val post = Posts(
40+
title = createPostDto.title,
41+
content = createPostDto.content,
42+
showAtMain = createPostDto.showAtMain
43+
)
44+
45+
val savedPost = postRepository.save(post)
46+
return postMapper.toDto(savedPost)
47+
}
48+
49+
fun update(id: Long, postDto: PostDto): PostDto {
50+
val existingPost = postRepository.findById(id)
51+
.orElseThrow { ResponseStatusException(HttpStatus.NOT_FOUND, "Post not found") }
52+
53+
existingPost.title = postDto.title
54+
existingPost.content = postDto.content
55+
existingPost.showAtMain = postDto.showAtMain
56+
57+
val updatedPost = postRepository.save(existingPost)
58+
return postMapper.toDto(updatedPost)
59+
}
60+
61+
fun delete(id: Long) {
62+
if (!postRepository.existsById(id)) {
63+
throw ResponseStatusException(HttpStatus.NOT_FOUND, "Post not found")
64+
}
65+
postRepository.deleteById(id)
66+
}
67+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package ru.codebattles.backend.web.controllers
2+
3+
import io.swagger.v3.oas.annotations.Operation
4+
import io.swagger.v3.oas.annotations.security.SecurityRequirement
5+
import io.swagger.v3.oas.annotations.tags.Tag
6+
import jakarta.annotation.security.RolesAllowed
7+
import org.springframework.http.HttpStatus
8+
import org.springframework.http.ResponseEntity
9+
import org.springframework.security.core.annotation.AuthenticationPrincipal
10+
import org.springframework.web.bind.annotation.*
11+
import ru.codebattles.backend.dto.CreatePostDto
12+
import ru.codebattles.backend.dto.PostDto
13+
import ru.codebattles.backend.entity.User
14+
import ru.codebattles.backend.services.PostService
15+
16+
@Tag(name = "Posts", description = "Endpoints for managing posts")
17+
@RestController
18+
@RequestMapping("/api/posts")
19+
@SecurityRequirement(name = "JWT")
20+
class PostController(
21+
private val postService: PostService,
22+
) {
23+
24+
@Operation(
25+
summary = "Get all posts",
26+
description = "Retrieves a list of all posts."
27+
)
28+
@GetMapping
29+
fun getAll(): List<PostDto> {
30+
return postService.getAll()
31+
}
32+
33+
@Operation(
34+
summary = "Get main page posts",
35+
description = "Retrieves posts that should be shown on the main page."
36+
)
37+
@GetMapping("/main")
38+
fun getMainPagePosts(): List<PostDto> {
39+
return postService.getMainPagePosts()
40+
}
41+
42+
@Operation(
43+
summary = "Get post by ID",
44+
description = "Retrieves a post by its ID."
45+
)
46+
@GetMapping("/{id}")
47+
fun getById(@PathVariable id: Long): PostDto {
48+
return postService.getById(id)
49+
}
50+
51+
@Operation(
52+
summary = "[ADMIN] Create a new post",
53+
description = "Creates a new post. Required admin role."
54+
)
55+
@RolesAllowed("ADMIN")
56+
@PostMapping
57+
fun create(@RequestBody createPostDto: CreatePostDto, @AuthenticationPrincipal user: User): PostDto {
58+
return postService.create(createPostDto)
59+
}
60+
61+
@Operation(
62+
summary = "[ADMIN] Update a post",
63+
description = "Updates an existing post by ID. Required admin role."
64+
)
65+
@RolesAllowed("ADMIN")
66+
@PutMapping("/{id}")
67+
fun update(@PathVariable id: Long, @RequestBody postDto: PostDto, @AuthenticationPrincipal user: User): PostDto {
68+
return postService.update(id, postDto)
69+
}
70+
71+
@Operation(
72+
summary = "[ADMIN] Delete a post",
73+
description = "Deletes a post by ID. Required admin role."
74+
)
75+
@RolesAllowed("ADMIN")
76+
@DeleteMapping("/{id}")
77+
fun delete(@PathVariable id: Long, @AuthenticationPrincipal user: User): ResponseEntity<Void> {
78+
postService.delete(id)
79+
return ResponseEntity.noContent().build()
80+
}
81+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CREATE TABLE posts
2+
(
3+
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
4+
created_at TIMESTAMP WITHOUT TIME ZONE,
5+
updated_at TIMESTAMP WITHOUT TIME ZONE,
6+
title VARCHAR(255) NOT NULL,
7+
content VARCHAR(255) NOT NULL,
8+
show_at_main BOOLEAN NOT NULL,
9+
CONSTRAINT pk_posts PRIMARY KEY (id)
10+
);

FRONTEND_V2/src/App.jsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ import {AdminProblemsPageImportFromPolygon} from "./pages/admin/problems/AdminPr
3535
import ChangeLanguagePage from "./pages/shared/ChangeLanguagePage.jsx";
3636
import RegisterPage from "./pages/shared/RegisterPage.jsx";
3737
import {AdminProblemsPageImportFromJSON} from "./pages/admin/problems/AdminProblemsPageImportFromJSON.jsx";
38+
import {PostPage} from "./pages/shared/PostPage.jsx";
39+
import {AdminPostPage} from "./pages/admin/pages/AdminPostPage.jsx";
40+
import {AdminPostsPageCreate} from "./pages/admin/pages/AdminPostsPageCreate.jsx";
41+
import {AdminPostsPageEdit} from "./pages/admin/pages/AdminPostsPageEdit.jsx";
3842

3943
import("../node_modules/bootstrap/dist/js/bootstrap.min.js")
4044

@@ -51,6 +55,8 @@ function App() {
5155
<Route path="/" element={<LoginPage/>}/>
5256
<Route path="/register" element={<RegisterPage/>}/>
5357

58+
<Route path="/posts/:postId" element={<PostPage/>}/>
59+
5460
<Route path="/problems" element={<ProblemsListPage/>}/>
5561
<Route path="/champs/:compId/problems" element={<ProblemsListPage/>}/>
5662
<Route path="/champs/:compId/problems/:id" element={<SeeProblemPage/>}/>
@@ -69,10 +75,17 @@ function App() {
6975
<Route path="/admin/problems/import/polygon" element={<AdminProblemsPageImportFromPolygon/>}/>
7076
<Route path="/admin/problems/import/json" element={<AdminProblemsPageImportFromJSON/>}/>
7177
<Route path="/admin/problems/:probId/edit" element={<AdminProblemsPageEdit/>}/>
78+
79+
<Route path="/admin/posts" element={<AdminPostPage/>}/>
80+
<Route path="/admin/posts/create" element={<AdminPostsPageCreate/>}/>
81+
<Route path="/admin/posts/:postId/edit" element={<AdminPostsPageEdit/>}/>
82+
7283
<Route path="/admin/champs" element={<AdminChampsPage/>}/>
84+
7385
<Route path="/admin/checkers" element={<AdminCheckersPage/>}/>
7486
<Route path="/admin/checkers/:checkId/edit" element={<AdminCheckersEditPage/>}/>
7587
<Route path="/admin/checkers/create" element={<AdminCheckersCreatePage/>}/>
88+
7689
<Route path="/admin/champs/:compId/edit" element={<AdminChampsDetailPage/>}/>
7790
<Route path="/admin/champs/create" element={<AdminChampsCreate/>}/>
7891
<Route path="/admin/champs/:compId/edit/users" element={<AdminUsersDetailPage/>}/>

0 commit comments

Comments
 (0)