Skip to content

Commit 78f899d

Browse files
add book features
1 parent 7f893c8 commit 78f899d

File tree

6 files changed

+289
-10
lines changed

6 files changed

+289
-10
lines changed

internal/handlers/book.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package handlers
2+
3+
import (
4+
"net/http"
5+
"strconv"
6+
7+
"github.com/gin-gonic/gin"
8+
"github.com/go-playground/validator/v10"
9+
"github.com/nevzattalhaozcan/forgotten/internal/models"
10+
"github.com/nevzattalhaozcan/forgotten/internal/services"
11+
)
12+
13+
type BookHandler struct {
14+
bookService *services.BookService
15+
validator *validator.Validate
16+
}
17+
18+
func NewBookHandler(bookService *services.BookService) *BookHandler {
19+
return &BookHandler{
20+
bookService: bookService,
21+
validator: validator.New(),
22+
}
23+
}
24+
25+
//TODO: Add swagger comments
26+
func (h *BookHandler) CreateBook(c *gin.Context) {
27+
var req models.CreateBookRequest
28+
if err := c.ShouldBindJSON(&req); err != nil {
29+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
30+
return
31+
}
32+
33+
if err := h.validator.Struct(&req); err != nil {
34+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
35+
return
36+
}
37+
38+
book, err := h.bookService.CreateBook(&req)
39+
if err != nil {
40+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
41+
return
42+
}
43+
44+
c.JSON(http.StatusCreated, gin.H{
45+
"message": "book created successfully",
46+
"book": book,
47+
})
48+
}
49+
50+
func (h *BookHandler) GetBookByID(c *gin.Context) {
51+
idParam := c.Param("id")
52+
id, err := strconv.ParseUint(idParam, 10, 64)
53+
if err != nil {
54+
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid book ID"})
55+
return
56+
}
57+
58+
book, err := h.bookService.GetBookByID(uint(id))
59+
if err != nil {
60+
if err.Error() == "book not found" {
61+
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
62+
return
63+
}
64+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
65+
return
66+
}
67+
68+
c.JSON(http.StatusOK, gin.H{ "book": book })
69+
}
70+
71+
func (h *BookHandler) UpdateBook(c *gin.Context) {
72+
idParam := c.Param("id")
73+
id, err := strconv.ParseUint(idParam, 10, 64)
74+
if err != nil {
75+
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid book ID"})
76+
return
77+
}
78+
79+
var req models.UpdateBookRequest
80+
if err := c.ShouldBindJSON(&req); err != nil {
81+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
82+
return
83+
}
84+
85+
if err := h.validator.Struct(&req); err != nil {
86+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
87+
return
88+
}
89+
90+
book, err := h.bookService.UpdateBook(uint(id), &req)
91+
if err != nil {
92+
if err.Error() == "book not found" {
93+
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
94+
return
95+
}
96+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
97+
return
98+
}
99+
100+
c.JSON(http.StatusOK, gin.H{
101+
"message": "book updated successfully",
102+
"book": book,
103+
})
104+
}
105+
106+
func (h *BookHandler) DeleteBook(c *gin.Context) {
107+
idParam := c.Param("id")
108+
id, err := strconv.ParseUint(idParam, 10, 64)
109+
if err != nil {
110+
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid book ID"})
111+
return
112+
}
113+
114+
if err := h.bookService.DeleteBook(uint(id)); err != nil {
115+
if err.Error() == "book not found" {
116+
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
117+
return
118+
}
119+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
120+
return
121+
}
122+
123+
c.Status(http.StatusNoContent)
124+
}
125+
126+
func (h *BookHandler) ListBooks(c *gin.Context) {
127+
books, err := h.bookService.ListBooks()
128+
if err != nil {
129+
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
130+
return
131+
}
132+
133+
c.JSON(http.StatusOK, gin.H{"books": books})
134+
}

internal/handlers/server.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func (s *Server) setupRoutes() {
4949
var userRepo repository.UserRepository = repository.NewUserRepository(s.db)
5050
var clubRepo repository.ClubRepository = repository.NewClubRepository(s.db)
5151
var eventRepo repository.EventRepository = repository.NewEventRepository(s.db)
52+
var bookRepo repository.BookRepository = repository.NewBookRepository(s.db)
5253

5354
var rdbAvailable bool
5455
var ttl time.Duration
@@ -79,6 +80,9 @@ func (s *Server) setupRoutes() {
7980
eventService := services.NewEventService(eventRepo, clubRepo, s.config)
8081
eventHandler := NewEventHandler(eventService)
8182

83+
bookService := services.NewBookService(bookRepo, s.config)
84+
bookHandler := NewBookHandler(bookService)
85+
8286
if s.config.Server.Environment != "production" {
8387
s.router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
8488
s.router.GET("/metrics", gin.WrapH(promhttp.Handler()))
@@ -120,6 +124,12 @@ func (s *Server) setupRoutes() {
120124

121125
protected.POST("/events/:id/rsvp", eventHandler.RSVPToEvent)
122126
protected.GET("/events/:id/attendees", eventHandler.GetEventAttendees)
127+
128+
protected.POST("/books", bookHandler.CreateBook)
129+
protected.GET("/books/:id", bookHandler.GetBookByID)
130+
protected.PUT("/books/:id", bookHandler.UpdateBook)
131+
protected.DELETE("/books/:id", bookHandler.DeleteBook)
132+
protected.GET("/books", bookHandler.ListBooks)
123133
}
124134
}
125135

internal/models/book.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ type Book struct {
1818
Description *string `json:"description,omitempty" gorm:"type:text"`
1919
Rating *float32 `json:"rating,omitempty" gorm:"type:decimal(2,1)" validate:"omitempty,gte=0,lte=5"`
2020

21-
CreatedAt time.Time `json:"created_at"`
22-
UpdatedAt time.Time `json:"updated_at"`
21+
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
22+
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
2323
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
2424
}
2525

internal/repository/book.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@ import (
55
"gorm.io/gorm"
66
)
77

8-
type BookRepository struct {
8+
type bookRepository struct {
99
db *gorm.DB
1010
}
1111

12-
func NewBookRepository(db *gorm.DB) *BookRepository {
13-
return &BookRepository{db: db}
12+
func NewBookRepository(db *gorm.DB) *bookRepository {
13+
return &bookRepository{db: db}
1414
}
1515

16-
func (r *BookRepository) Create(book *models.Book) error {
16+
func (r *bookRepository) Create(book *models.Book) error {
1717
return r.db.Create(book).Error
1818
}
1919

20-
func (r *BookRepository) GetByID(id uint) (*models.Book, error) {
20+
func (r *bookRepository) GetByID(id uint) (*models.Book, error) {
2121
var book models.Book
2222
err := r.db.First(&book, id).Error
2323
if err != nil {
@@ -26,15 +26,15 @@ func (r *BookRepository) GetByID(id uint) (*models.Book, error) {
2626
return &book, nil
2727
}
2828

29-
func (r *BookRepository) Update(book *models.Book) error {
29+
func (r *bookRepository) Update(book *models.Book) error {
3030
return r.db.Save(book).Error
3131
}
3232

33-
func (r *BookRepository) Delete(id uint) error {
33+
func (r *bookRepository) Delete(id uint) error {
3434
return r.db.Delete(&models.Book{}, id).Error
3535
}
3636

37-
func (r *BookRepository) List(limit, offset int) ([]*models.Book, error) {
37+
func (r *bookRepository) List(limit, offset int) ([]*models.Book, error) {
3838
var books []*models.Book
3939
err := r.db.Limit(limit).Offset(offset).Find(&books).Error
4040
if err != nil {

internal/repository/interfaces.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,12 @@ type EventRepository interface {
3636
Delete(id uint) error
3737
RSVP(eventID uint, rsvp *models.EventRSVP) error
3838
GetEventAttendees(eventID uint) ([]models.EventRSVP, error)
39+
}
40+
41+
type BookRepository interface {
42+
Create(book *models.Book) error
43+
GetByID(id uint) (*models.Book, error)
44+
Update(book *models.Book) error
45+
Delete(id uint) error
46+
List(limit, offset int) ([]*models.Book, error)
3947
}

internal/services/book.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package services
2+
3+
import (
4+
"errors"
5+
6+
"github.com/nevzattalhaozcan/forgotten/internal/config"
7+
"github.com/nevzattalhaozcan/forgotten/internal/models"
8+
"github.com/nevzattalhaozcan/forgotten/internal/repository"
9+
"gorm.io/gorm"
10+
)
11+
12+
type BookService struct {
13+
bookRepo repository.BookRepository
14+
config *config.Config
15+
}
16+
17+
func NewBookService(bookRepo repository.BookRepository, config *config.Config) *BookService {
18+
return &BookService{
19+
bookRepo: bookRepo,
20+
config: config,
21+
}
22+
}
23+
24+
func (s *BookService) CreateBook(req *models.CreateBookRequest) (*models.BookResponse, error) {
25+
book := &models.Book{
26+
Title: req.Title,
27+
Author: req.Author,
28+
CoverURL: req.CoverURL,
29+
Genre: req.Genre,
30+
Pages: req.Pages,
31+
PublishedYear: req.PublishedYear,
32+
ISBN: req.ISBN,
33+
Description: req.Description,
34+
}
35+
36+
if err := s.bookRepo.Create(book); err != nil {
37+
return nil, err
38+
}
39+
40+
response := book.ToResponse()
41+
return &response, nil
42+
}
43+
44+
func (s *BookService) GetBookByID(id uint) (*models.BookResponse, error) {
45+
book, err := s.bookRepo.GetByID(id)
46+
if err != nil {
47+
if errors.Is(err, gorm.ErrRecordNotFound) {
48+
return nil, errors.New("book not found")
49+
}
50+
return nil, err
51+
}
52+
53+
response := book.ToResponse()
54+
return &response, nil
55+
}
56+
57+
func (s *BookService) UpdateBook(id uint, req *models.UpdateBookRequest) (*models.BookResponse, error) {
58+
book, err := s.bookRepo.GetByID(id)
59+
if err != nil {
60+
if errors.Is(err, gorm.ErrRecordNotFound) {
61+
return nil, errors.New("book not found")
62+
}
63+
return nil, err
64+
}
65+
66+
if req.Title != nil {
67+
book.Title = *req.Title
68+
}
69+
if req.Author != nil {
70+
book.Author = req.Author
71+
}
72+
if req.CoverURL != nil {
73+
book.CoverURL = req.CoverURL
74+
}
75+
if req.Genre != nil {
76+
book.Genre = req.Genre
77+
}
78+
if req.Pages != nil {
79+
book.Pages = req.Pages
80+
}
81+
if req.PublishedYear != nil {
82+
book.PublishedYear = req.PublishedYear
83+
}
84+
if req.ISBN != nil {
85+
book.ISBN = req.ISBN
86+
}
87+
if req.Description != nil {
88+
book.Description = req.Description
89+
}
90+
if req.Rating != nil {
91+
book.Rating = req.Rating
92+
}
93+
94+
if err := s.bookRepo.Update(book); err != nil {
95+
return nil, err
96+
}
97+
98+
response := book.ToResponse()
99+
return &response, nil
100+
}
101+
102+
func (s *BookService) DeleteBook(id uint) error {
103+
_, err := s.bookRepo.GetByID(id)
104+
if err != nil {
105+
if errors.Is(err, gorm.ErrRecordNotFound) {
106+
return errors.New("book not found")
107+
}
108+
return err
109+
}
110+
111+
return s.bookRepo.Delete(id)
112+
}
113+
114+
func (s *BookService) ListBooks() ([]*models.BookResponse, error) {
115+
books, err := s.bookRepo.List(50, 0)
116+
if err != nil {
117+
return nil, err
118+
}
119+
120+
var responses []*models.BookResponse
121+
for _, book := range books {
122+
response := book.ToResponse()
123+
responses = append(responses, &response)
124+
}
125+
126+
return responses, nil
127+
}

0 commit comments

Comments
 (0)