From faecc65a467463a0edbb342f285357ea5ac70ba8 Mon Sep 17 00:00:00 2001 From: hanif salafi Date: Wed, 17 Sep 2025 01:21:27 +0700 Subject: [PATCH] feat: add bookmarks --- app/database/entity/bookmarks.entity.go | 21 + app/database/index.database.go | 1 + .../activity_logs/activity_logs.module.go | 5 +- app/module/bookmarks/bookmarks.module.go | 66 +++ .../controller/bookmarks.controller.go | 342 +++++++++++ .../bookmarks/mapper/bookmarks.mapper.go | 55 ++ .../repository/bookmarks.repository.go | 217 +++++++ .../bookmarks/request/bookmarks.request.go | 63 +++ .../bookmarks/response/bookmarks.response.go | 46 ++ .../bookmarks/service/bookmarks.service.go | 244 ++++++++ app/router/api.go | 5 + config/toml/config.toml | 2 +- docs/swagger/docs.go | 529 ++++++++++++++++++ docs/swagger/swagger.json | 529 ++++++++++++++++++ docs/swagger/swagger.yaml | 339 +++++++++++ main.go | 2 + 16 files changed, 2463 insertions(+), 3 deletions(-) create mode 100644 app/database/entity/bookmarks.entity.go create mode 100644 app/module/bookmarks/bookmarks.module.go create mode 100644 app/module/bookmarks/controller/bookmarks.controller.go create mode 100644 app/module/bookmarks/mapper/bookmarks.mapper.go create mode 100644 app/module/bookmarks/repository/bookmarks.repository.go create mode 100644 app/module/bookmarks/request/bookmarks.request.go create mode 100644 app/module/bookmarks/response/bookmarks.response.go create mode 100644 app/module/bookmarks/service/bookmarks.service.go diff --git a/app/database/entity/bookmarks.entity.go b/app/database/entity/bookmarks.entity.go new file mode 100644 index 0000000..547d415 --- /dev/null +++ b/app/database/entity/bookmarks.entity.go @@ -0,0 +1,21 @@ +package entity + +import ( + "time" + + "github.com/google/uuid" +) + +type Bookmarks struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + UserId uint `json:"user_id" gorm:"type:int4;not null"` + ArticleId uint `json:"article_id" gorm:"type:int4;not null"` + ClientId *uuid.UUID `json:"client_id" gorm:"type:UUID"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relations + User Users `json:"user" gorm:"foreignKey:UserId;references:ID"` + Article Articles `json:"article" gorm:"foreignKey:ArticleId;references:ID"` +} diff --git a/app/database/index.database.go b/app/database/index.database.go index f838647..4d393fc 100644 --- a/app/database/index.database.go +++ b/app/database/index.database.go @@ -98,6 +98,7 @@ func Models() []interface{} { entity.ArticleComments{}, entity.ArticleNulisAI{}, entity.AuditTrails{}, + entity.Bookmarks{}, entity.Cities{}, entity.Clients{}, entity.ClientApprovalSettings{}, diff --git a/app/module/activity_logs/activity_logs.module.go b/app/module/activity_logs/activity_logs.module.go index 501dfe3..442f836 100644 --- a/app/module/activity_logs/activity_logs.module.go +++ b/app/module/activity_logs/activity_logs.module.go @@ -1,11 +1,12 @@ package activity_logs import ( - "github.com/gofiber/fiber/v2" - "go.uber.org/fx" "web-medols-be/app/module/activity_logs/controller" "web-medols-be/app/module/activity_logs/repository" "web-medols-be/app/module/activity_logs/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" ) // struct of ActivityLogsRouter diff --git a/app/module/bookmarks/bookmarks.module.go b/app/module/bookmarks/bookmarks.module.go new file mode 100644 index 0000000..4b6f3a7 --- /dev/null +++ b/app/module/bookmarks/bookmarks.module.go @@ -0,0 +1,66 @@ +package bookmarks + +import ( + "web-medols-be/app/middleware" + "web-medols-be/app/module/bookmarks/controller" + "web-medols-be/app/module/bookmarks/repository" + "web-medols-be/app/module/bookmarks/service" + usersRepo "web-medols-be/app/module/users/repository" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// BookmarksRouter struct of BookmarksRouter +type BookmarksRouter struct { + App fiber.Router + Controller controller.BookmarksController + UsersRepo usersRepo.UsersRepository +} + +// NewBookmarksModule register bulky of Bookmarks module +var NewBookmarksModule = fx.Options( + // register repository of Bookmarks module + fx.Provide(repository.NewBookmarksRepository), + + // register service of Bookmarks module + fx.Provide(service.NewBookmarksService), + + // register controller of Bookmarks module + fx.Provide(controller.NewBookmarksController), + + // register router of Bookmarks module + fx.Provide(NewBookmarksRouter), +) + +// NewBookmarksRouter init BookmarksRouter +func NewBookmarksRouter(fiber *fiber.App, controller controller.BookmarksController, usersRepo usersRepo.UsersRepository) *BookmarksRouter { + return &BookmarksRouter{ + App: fiber, + Controller: controller, + UsersRepo: usersRepo, + } +} + +// RegisterBookmarksRoutes register routes of Bookmarks module +func (_i *BookmarksRouter) RegisterBookmarksRoutes() { + // define controllers + bookmarksController := _i.Controller + + // define routes + _i.App.Route("/bookmarks", func(router fiber.Router) { + // Add user middleware to extract user level from JWT token + router.Use(middleware.UserMiddleware(_i.UsersRepo)) + + // Public routes (require authentication) + router.Get("/", bookmarksController.All) + router.Get("/:id", bookmarksController.Show) + router.Post("/", bookmarksController.Save) + router.Delete("/:id", bookmarksController.Delete) + + // User-specific routes + router.Get("/user", bookmarksController.GetByUserId) + router.Post("/toggle/:articleId", bookmarksController.ToggleBookmark) + router.Get("/summary", bookmarksController.GetBookmarkSummary) + }) +} diff --git a/app/module/bookmarks/controller/bookmarks.controller.go b/app/module/bookmarks/controller/bookmarks.controller.go new file mode 100644 index 0000000..300525f --- /dev/null +++ b/app/module/bookmarks/controller/bookmarks.controller.go @@ -0,0 +1,342 @@ +package controller + +import ( + "strconv" + "web-medols-be/app/middleware" + "web-medols-be/app/module/bookmarks/request" + "web-medols-be/app/module/bookmarks/service" + "web-medols-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "web-medols-be/utils/response" + utilVal "web-medols-be/utils/validator" +) + +type bookmarksController struct { + bookmarksService service.BookmarksService + Log zerolog.Logger +} + +type BookmarksController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + GetByUserId(c *fiber.Ctx) error + ToggleBookmark(c *fiber.Ctx) error + GetBookmarkSummary(c *fiber.Ctx) error +} + +func NewBookmarksController(bookmarksService service.BookmarksService, log zerolog.Logger) BookmarksController { + return &bookmarksController{ + bookmarksService: bookmarksService, + Log: log, + } +} + +// All Bookmarks +// @Summary Get all Bookmarks +// @Description API for getting all Bookmarks +// @Tags Bookmarks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req query request.BookmarksQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /bookmarks [get] +func (_i *bookmarksController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.BookmarksQueryRequestContext{ + ArticleId: c.Query("articleId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + _i.Log.Info().Interface("clientId", clientId).Msg("") + + // Get Authorization token from header + authToken := c.Get("Authorization") + + bookmarksData, paging, err := _i.bookmarksService.All(clientId, authToken, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Successfully retrieved bookmarks"}, + Data: bookmarksData, + Meta: &paging, + }) +} + +// Show Bookmark +// @Summary Get Bookmark by ID +// @Description API for getting Bookmark by ID +// @Tags Bookmarks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Bookmark ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /bookmarks/{id} [get] +func (_i *bookmarksController) Show(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Invalid bookmark ID"}, + }) + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + _i.Log.Info().Interface("clientId", clientId).Msg("") + + bookmarkData, err := _i.bookmarksService.Show(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Successfully retrieved bookmark"}, + Data: bookmarkData, + }) +} + +// Save Bookmark +// @Summary Create new Bookmark +// @Description API for creating new Bookmark +// @Tags Bookmarks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req body request.BookmarksCreateRequest true "Bookmark data" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /bookmarks [post] +func (_i *bookmarksController) Save(c *fiber.Ctx) error { + var req request.BookmarksCreateRequest + if err := c.BodyParser(&req); err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Invalid request body"}, + }) + } + + // Validate request + if err := utilVal.ValidateStruct(req); err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Validation error"}, + Data: err, + }) + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Get Authorization token from header + authToken := c.Get("Authorization") + _i.Log.Info().Interface("clientId", clientId).Msg("") + _i.Log.Info().Str("authToken", authToken).Msg("") + + bookmarkData, err := _i.bookmarksService.Save(clientId, req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Successfully created bookmark"}, + Data: bookmarkData, + }) +} + +// Delete Bookmark +// @Summary Delete Bookmark +// @Description API for deleting Bookmark +// @Tags Bookmarks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Bookmark ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /bookmarks/{id} [delete] +func (_i *bookmarksController) Delete(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Invalid bookmark ID"}, + }) + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + _i.Log.Info().Interface("clientId", clientId).Msg("") + + err = _i.bookmarksService.Delete(clientId, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Successfully deleted bookmark"}, + }) +} + +// Get Bookmarks by User ID +// @Summary Get Bookmarks by User ID +// @Description API for getting Bookmarks by User ID +// @Tags Bookmarks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req query request.BookmarksQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /bookmarks/user [get] +func (_i *bookmarksController) GetByUserId(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.BookmarksQueryRequestContext{ + ArticleId: c.Query("articleId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Get Authorization token from header + authToken := c.Get("Authorization") + _i.Log.Info().Interface("clientId", clientId).Msg("") + _i.Log.Info().Str("authToken", authToken).Msg("") + + bookmarksData, paging, err := _i.bookmarksService.GetByUserId(clientId, authToken, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Successfully retrieved user bookmarks"}, + Data: bookmarksData, + Meta: &paging, + }) +} + +// Toggle Bookmark +// @Summary Toggle Bookmark (Add/Remove) +// @Description API for toggling bookmark status for an article +// @Tags Bookmarks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param articleId path int true "Article ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /bookmarks/toggle/{articleId} [post] +func (_i *bookmarksController) ToggleBookmark(c *fiber.Ctx) error { + articleId, err := strconv.Atoi(c.Params("articleId")) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Invalid article ID"}, + }) + } + + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Get Authorization token from header + authToken := c.Get("Authorization") + _i.Log.Info().Interface("clientId", clientId).Msg("") + _i.Log.Info().Str("authToken", authToken).Msg("") + + isBookmarked, err := _i.bookmarksService.ToggleBookmark(clientId, authToken, uint(articleId)) + if err != nil { + return err + } + + message := "Bookmark removed" + if isBookmarked { + message = "Bookmark added" + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{message}, + Data: map[string]interface{}{ + "isBookmarked": isBookmarked, + "articleId": articleId, + }, + }) +} + +// Get Bookmark Summary +// @Summary Get Bookmark Summary for User +// @Description API for getting bookmark summary including total count and recent bookmarks +// @Tags Bookmarks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /bookmarks/summary [get] +func (_i *bookmarksController) GetBookmarkSummary(c *fiber.Ctx) error { + // Get ClientId from context + clientId := middleware.GetClientID(c) + + // Get Authorization token from header + authToken := c.Get("Authorization") + _i.Log.Info().Interface("clientId", clientId).Msg("") + _i.Log.Info().Str("authToken", authToken).Msg("") + + summaryData, err := _i.bookmarksService.GetBookmarkSummary(clientId, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Successfully retrieved bookmark summary"}, + Data: summaryData, + }) +} diff --git a/app/module/bookmarks/mapper/bookmarks.mapper.go b/app/module/bookmarks/mapper/bookmarks.mapper.go new file mode 100644 index 0000000..b2e855b --- /dev/null +++ b/app/module/bookmarks/mapper/bookmarks.mapper.go @@ -0,0 +1,55 @@ +package mapper + +import ( + "web-medols-be/app/database/entity" + "web-medols-be/app/module/bookmarks/response" +) + +func ToBookmarksResponse(bookmark *entity.Bookmarks) *response.BookmarksResponse { + return &response.BookmarksResponse{ + ID: bookmark.ID, + UserId: bookmark.UserId, + ArticleId: bookmark.ArticleId, + IsActive: bookmark.IsActive, + CreatedAt: bookmark.CreatedAt, + UpdatedAt: bookmark.UpdatedAt, + Article: response.ArticleDetails{ + ID: bookmark.Article.ID, + Title: bookmark.Article.Title, + Slug: bookmark.Article.Slug, + Description: bookmark.Article.Description, + HtmlDescription: bookmark.Article.HtmlDescription, + CategoryId: bookmark.Article.CategoryId, + TypeId: bookmark.Article.TypeId, + Tags: bookmark.Article.Tags, + ThumbnailUrl: getThumbnailUrl(bookmark.Article.ThumbnailPath), + PageUrl: bookmark.Article.PageUrl, + CreatedById: bookmark.Article.CreatedById, + ShareCount: bookmark.Article.ShareCount, + ViewCount: bookmark.Article.ViewCount, + CommentCount: bookmark.Article.CommentCount, + StatusId: bookmark.Article.StatusId, + IsBanner: bookmark.Article.IsBanner, + IsPublish: bookmark.Article.IsPublish, + PublishedAt: bookmark.Article.PublishedAt, + IsActive: bookmark.Article.IsActive, + CreatedAt: bookmark.Article.CreatedAt, + UpdatedAt: bookmark.Article.UpdatedAt, + }, + } +} + +func ToBookmarksResponseList(bookmarks []entity.Bookmarks) []*response.BookmarksResponse { + var responses []*response.BookmarksResponse + for _, bookmark := range bookmarks { + responses = append(responses, ToBookmarksResponse(&bookmark)) + } + return responses +} + +func getThumbnailUrl(thumbnailPath *string) string { + if thumbnailPath != nil && *thumbnailPath != "" { + return *thumbnailPath + } + return "" +} diff --git a/app/module/bookmarks/repository/bookmarks.repository.go b/app/module/bookmarks/repository/bookmarks.repository.go new file mode 100644 index 0000000..9e8eca4 --- /dev/null +++ b/app/module/bookmarks/repository/bookmarks.repository.go @@ -0,0 +1,217 @@ +package repository + +import ( + "web-medols-be/app/database" + "web-medols-be/app/database/entity" + "web-medols-be/app/module/bookmarks/request" + "web-medols-be/utils/paginator" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +type bookmarksRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// BookmarksRepository define interface of IBookmarksRepository +type BookmarksRepository interface { + GetAll(clientId *uuid.UUID, req request.BookmarksQueryRequest) (bookmarks []*entity.Bookmarks, paging paginator.Pagination, err error) + FindOne(clientId *uuid.UUID, id uint) (bookmark *entity.Bookmarks, err error) + FindByUserAndArticle(clientId *uuid.UUID, userId uint, articleId uint) (bookmark *entity.Bookmarks, err error) + Create(clientId *uuid.UUID, bookmark *entity.Bookmarks) (bookmarkReturn *entity.Bookmarks, err error) + Update(clientId *uuid.UUID, id uint, bookmark *entity.Bookmarks) (err error) + Delete(clientId *uuid.UUID, id uint) (err error) + GetByUserId(clientId *uuid.UUID, userId uint, req request.BookmarksQueryRequest) (bookmarks []*entity.Bookmarks, paging paginator.Pagination, err error) + CountByUserId(clientId *uuid.UUID, userId uint) (count int64, err error) +} + +func NewBookmarksRepository(db *database.Database, log zerolog.Logger) BookmarksRepository { + return &bookmarksRepository{ + DB: db, + Log: log, + } +} + +// implement interface of IBookmarksRepository +func (_i *bookmarksRepository) GetAll(clientId *uuid.UUID, req request.BookmarksQueryRequest) (bookmarks []*entity.Bookmarks, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Bookmarks{}).Preload("User").Preload("Article") + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + // Apply filters + if req.UserId != nil { + query = query.Where("user_id = ?", *req.UserId) + } + + if req.ArticleId != nil { + query = query.Where("article_id = ?", *req.ArticleId) + } + + // Count total records + if err = query.Count(&count).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to count bookmarks") + return nil, paging, err + } + + // Apply pagination + if req.Pagination != nil { + offset := (req.Pagination.Page - 1) * req.Pagination.Limit + query = query.Offset(offset).Limit(req.Pagination.Limit) + paging = *req.Pagination + } + + // Execute query + if err = query.Find(&bookmarks).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to get bookmarks") + return nil, paging, err + } + + paging.Count = count + paging = *paginator.Paging(&paging) + + return bookmarks, paging, nil +} + +func (_i *bookmarksRepository) FindOne(clientId *uuid.UUID, id uint) (bookmark *entity.Bookmarks, err error) { + query := _i.DB.DB.Model(&entity.Bookmarks{}).Preload("User").Preload("Article") + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err = query.Where("id = ?", id).First(&bookmark).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to find bookmark") + return nil, err + } + + return bookmark, nil +} + +func (_i *bookmarksRepository) FindByUserAndArticle(clientId *uuid.UUID, userId uint, articleId uint) (bookmark *entity.Bookmarks, err error) { + query := _i.DB.DB.Model(&entity.Bookmarks{}) + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err = query.Where("user_id = ? AND article_id = ?", userId, articleId).First(&bookmark).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to find bookmark by user and article") + return nil, err + } + + return bookmark, nil +} + +func (_i *bookmarksRepository) Create(clientId *uuid.UUID, bookmark *entity.Bookmarks) (bookmarkReturn *entity.Bookmarks, err error) { + bookmark.ClientId = clientId + + if err = _i.DB.DB.Create(bookmark).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to create bookmark") + return nil, err + } + + return bookmark, nil +} + +func (_i *bookmarksRepository) Update(clientId *uuid.UUID, id uint, bookmark *entity.Bookmarks) (err error) { + query := _i.DB.DB.Model(&entity.Bookmarks{}) + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err = query.Where("id = ?", id).Updates(bookmark).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to update bookmark") + return err + } + + return nil +} + +func (_i *bookmarksRepository) Delete(clientId *uuid.UUID, id uint) (err error) { + query := _i.DB.DB.Model(&entity.Bookmarks{}) + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + if err = query.Where("id = ?", id).Delete(&entity.Bookmarks{}).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to delete bookmark") + return err + } + + return nil +} + +func (_i *bookmarksRepository) GetByUserId(clientId *uuid.UUID, userId uint, req request.BookmarksQueryRequest) (bookmarks []*entity.Bookmarks, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Bookmarks{}).Preload("User").Preload("Article") + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + // Apply user filter + query = query.Where("user_id = ?", userId) + + // Apply additional filters + if req.ArticleId != nil { + query = query.Where("article_id = ?", *req.ArticleId) + } + + // Count total records + if err = query.Count(&count).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to count user bookmarks") + return nil, paging, err + } + + // Apply pagination + if req.Pagination != nil { + offset := (req.Pagination.Page - 1) * req.Pagination.Limit + query = query.Offset(offset).Limit(req.Pagination.Limit) + paging = *req.Pagination + } + + // Execute query + if err = query.Find(&bookmarks).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to get user bookmarks") + return nil, paging, err + } + + paging.Count = count + paging = *paginator.Paging(&paging) + + return bookmarks, paging, nil +} + +func (_i *bookmarksRepository) CountByUserId(clientId *uuid.UUID, userId uint) (count int64, err error) { + query := _i.DB.DB.Model(&entity.Bookmarks{}) + + // Apply client filter + if clientId != nil { + query = query.Where("client_id = ?", clientId) + } + + // Apply user filter + query = query.Where("user_id = ?", userId) + + if err = query.Count(&count).Error; err != nil { + _i.Log.Error().Err(err).Msg("Failed to count user bookmarks") + return 0, err + } + + return count, nil +} diff --git a/app/module/bookmarks/request/bookmarks.request.go b/app/module/bookmarks/request/bookmarks.request.go new file mode 100644 index 0000000..be3a26f --- /dev/null +++ b/app/module/bookmarks/request/bookmarks.request.go @@ -0,0 +1,63 @@ +package request + +import ( + "strconv" + "time" + "web-medols-be/app/database/entity" + "web-medols-be/utils/paginator" +) + +type BookmarksGeneric interface { + ToEntity() +} + +type BookmarksQueryRequest struct { + UserId *uint `json:"userId"` + ArticleId *uint `json:"articleId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type BookmarksCreateRequest struct { + ArticleId uint `json:"articleId" validate:"required"` +} + +func (req BookmarksCreateRequest) ToEntity(userId uint) *entity.Bookmarks { + return &entity.Bookmarks{ + UserId: userId, + ArticleId: req.ArticleId, + IsActive: boolPtr(true), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } +} + +type BookmarksQueryRequestContext struct { + UserId string `json:"userId"` + ArticleId string `json:"articleId"` +} + +func (req BookmarksQueryRequestContext) ToParamRequest() BookmarksQueryRequest { + var request BookmarksQueryRequest + + if userIdStr := req.UserId; userIdStr != "" { + userId, err := strconv.Atoi(userIdStr) + if err == nil { + userIdUint := uint(userId) + request.UserId = &userIdUint + } + } + if articleIdStr := req.ArticleId; articleIdStr != "" { + articleId, err := strconv.Atoi(articleIdStr) + if err == nil { + articleIdUint := uint(articleId) + request.ArticleId = &articleIdUint + } + } + + return request +} + +// Helper function to create bool pointer +func boolPtr(b bool) *bool { + return &b +} diff --git a/app/module/bookmarks/response/bookmarks.response.go b/app/module/bookmarks/response/bookmarks.response.go new file mode 100644 index 0000000..3bd3520 --- /dev/null +++ b/app/module/bookmarks/response/bookmarks.response.go @@ -0,0 +1,46 @@ +package response + +import ( + "time" +) + +type BookmarksResponse struct { + ID uint `json:"id"` + UserId uint `json:"userId"` + ArticleId uint `json:"articleId"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + // Article details + Article ArticleDetails `json:"article"` +} + +type ArticleDetails struct { + ID uint `json:"id"` + Title string `json:"title"` + Slug string `json:"slug"` + Description string `json:"description"` + HtmlDescription string `json:"htmlDescription"` + CategoryId int `json:"categoryId"` + TypeId int `json:"typeId"` + Tags string `json:"tags"` + ThumbnailUrl string `json:"thumbnailUrl"` + PageUrl *string `json:"pageUrl"` + CreatedById *uint `json:"createdById"` + ShareCount *int `json:"shareCount"` + ViewCount *int `json:"viewCount"` + CommentCount *int `json:"commentCount"` + StatusId *int `json:"statusId"` + IsBanner *bool `json:"isBanner"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type BookmarksSummaryResponse struct { + TotalBookmarks int `json:"totalBookmarks"` + RecentBookmarks []*BookmarksResponse `json:"recentBookmarks"` +} diff --git a/app/module/bookmarks/service/bookmarks.service.go b/app/module/bookmarks/service/bookmarks.service.go new file mode 100644 index 0000000..6b7139f --- /dev/null +++ b/app/module/bookmarks/service/bookmarks.service.go @@ -0,0 +1,244 @@ +package service + +import ( + "errors" + "web-medols-be/app/database/entity" + articlesRepository "web-medols-be/app/module/articles/repository" + "web-medols-be/app/module/bookmarks/mapper" + "web-medols-be/app/module/bookmarks/repository" + "web-medols-be/app/module/bookmarks/request" + "web-medols-be/app/module/bookmarks/response" + usersRepository "web-medols-be/app/module/users/repository" + "web-medols-be/utils/paginator" + utilSvc "web-medols-be/utils/service" + + "github.com/google/uuid" + "github.com/rs/zerolog" +) + +// BookmarksService +type bookmarksService struct { + Repo repository.BookmarksRepository + ArticlesRepo articlesRepository.ArticlesRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +// BookmarksService define interface of IBookmarksService +type BookmarksService interface { + All(clientId *uuid.UUID, authToken string, req request.BookmarksQueryRequest) (bookmarks []*response.BookmarksResponse, paging paginator.Pagination, err error) + Show(clientId *uuid.UUID, id uint) (bookmark *response.BookmarksResponse, err error) + Save(clientId *uuid.UUID, req request.BookmarksCreateRequest, authToken string) (bookmark *entity.Bookmarks, err error) + Delete(clientId *uuid.UUID, id uint) error + GetByUserId(clientId *uuid.UUID, authToken string, req request.BookmarksQueryRequest) (bookmarks []*response.BookmarksResponse, paging paginator.Pagination, err error) + ToggleBookmark(clientId *uuid.UUID, authToken string, articleId uint) (isBookmarked bool, err error) + GetBookmarkSummary(clientId *uuid.UUID, authToken string) (summary *response.BookmarksSummaryResponse, err error) +} + +// NewBookmarksService init BookmarksService +func NewBookmarksService( + repo repository.BookmarksRepository, + articlesRepo articlesRepository.ArticlesRepository, + usersRepo usersRepository.UsersRepository, + log zerolog.Logger, +) BookmarksService { + return &bookmarksService{ + Repo: repo, + ArticlesRepo: articlesRepo, + UsersRepo: usersRepo, + Log: log, + } +} + +// implement interface of IBookmarksService +func (_i *bookmarksService) All(clientId *uuid.UUID, authToken string, req request.BookmarksQueryRequest) (bookmarks []*response.BookmarksResponse, paging paginator.Pagination, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + _i.Log.Error().Msg("User not found from auth token") + return nil, paging, errors.New("user not found") + } + req.UserId = &user.ID + + bookmarksEntity, paging, err := _i.Repo.GetAll(clientId, req) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to get all bookmarks") + return nil, paging, err + } + + // Convert []*entity.Bookmarks to []entity.Bookmarks + var bookmarksSlice []entity.Bookmarks + for _, b := range bookmarksEntity { + bookmarksSlice = append(bookmarksSlice, *b) + } + bookmarks = mapper.ToBookmarksResponseList(bookmarksSlice) + return bookmarks, paging, nil +} + +func (_i *bookmarksService) Show(clientId *uuid.UUID, id uint) (bookmark *response.BookmarksResponse, err error) { + bookmarkEntity, err := _i.Repo.FindOne(clientId, id) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to show bookmark") + return nil, err + } + + bookmark = mapper.ToBookmarksResponse(bookmarkEntity) + return bookmark, nil +} + +func (_i *bookmarksService) Save(clientId *uuid.UUID, req request.BookmarksCreateRequest, authToken string) (bookmark *entity.Bookmarks, err error) { + // Extract user info from auth token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + _i.Log.Error().Msg("User not found from auth token") + return nil, errors.New("user not found") + } + + // Check if article exists + _, err = _i.ArticlesRepo.FindOne(clientId, req.ArticleId) + if err != nil { + _i.Log.Error().Err(err).Msg("Article not found") + return nil, errors.New("article not found") + } + + // Check if bookmark already exists + existingBookmark, err := _i.Repo.FindByUserAndArticle(clientId, user.ID, req.ArticleId) + if err == nil && existingBookmark != nil { + _i.Log.Error().Msg("Bookmark already exists") + return nil, errors.New("article already bookmarked") + } + + // Create new bookmark + bookmarkEntity := req.ToEntity(user.ID) + bookmark, err = _i.Repo.Create(clientId, bookmarkEntity) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to create bookmark") + return nil, err + } + + return bookmark, nil +} + +func (_i *bookmarksService) Delete(clientId *uuid.UUID, id uint) error { + err := _i.Repo.Delete(clientId, id) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to delete bookmark") + return err + } + + return nil +} + +func (_i *bookmarksService) GetByUserId(clientId *uuid.UUID, authToken string, req request.BookmarksQueryRequest) (bookmarks []*response.BookmarksResponse, paging paginator.Pagination, err error) { + // Extract user info from auth token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + _i.Log.Error().Msg("User not found from auth token") + return nil, paging, errors.New("user not found") + } + + bookmarksEntity, paging, err := _i.Repo.GetByUserId(clientId, user.ID, req) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to get bookmarks by user ID") + return nil, paging, err + } + + // Convert []*entity.Bookmarks to []entity.Bookmarks + var bookmarksSlice []entity.Bookmarks + for _, b := range bookmarksEntity { + bookmarksSlice = append(bookmarksSlice, *b) + } + bookmarks = mapper.ToBookmarksResponseList(bookmarksSlice) + return bookmarks, paging, nil +} + +func (_i *bookmarksService) ToggleBookmark(clientId *uuid.UUID, authToken string, articleId uint) (isBookmarked bool, err error) { + // Extract user info from auth token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + _i.Log.Error().Msg("User not found from auth token") + return false, errors.New("user not found") + } + + // Check if article exists + _, err = _i.ArticlesRepo.FindOne(clientId, articleId) + if err != nil { + _i.Log.Error().Err(err).Msg("Article not found") + return false, errors.New("article not found") + } + + // Check if bookmark already exists + existingBookmark, err := _i.Repo.FindByUserAndArticle(clientId, user.ID, articleId) + if err == nil && existingBookmark != nil { + // Bookmark exists, delete it + err = _i.Repo.Delete(clientId, existingBookmark.ID) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to delete existing bookmark") + return false, err + } + return false, nil // Bookmark removed + } + + // Bookmark doesn't exist, create it + bookmarkEntity := &entity.Bookmarks{ + UserId: user.ID, + ArticleId: articleId, + IsActive: boolPtr(true), + } + + _, err = _i.Repo.Create(clientId, bookmarkEntity) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to create bookmark") + return false, err + } + + return true, nil // Bookmark added +} + +func (_i *bookmarksService) GetBookmarkSummary(clientId *uuid.UUID, authToken string) (summary *response.BookmarksSummaryResponse, err error) { + // Extract user info from auth token + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + _i.Log.Error().Msg("User not found from auth token") + return nil, errors.New("user not found") + } + + // Get total count + totalCount, err := _i.Repo.CountByUserId(clientId, user.ID) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to count user bookmarks") + return nil, err + } + + // Get recent bookmarks (last 5) + req := request.BookmarksQueryRequest{ + Pagination: &paginator.Pagination{ + Page: 1, + Limit: 5, + }, + } + + recentBookmarksEntity, _, err := _i.Repo.GetByUserId(clientId, user.ID, req) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to get recent bookmarks") + return nil, err + } + + // Convert []*entity.Bookmarks to []entity.Bookmarks + var bookmarksSlice []entity.Bookmarks + for _, b := range recentBookmarksEntity { + bookmarksSlice = append(bookmarksSlice, *b) + } + recentBookmarks := mapper.ToBookmarksResponseList(bookmarksSlice) + + summary = &response.BookmarksSummaryResponse{ + TotalBookmarks: int(totalCount), + RecentBookmarks: recentBookmarks, + } + + return summary, nil +} + +// Helper function to create bool pointer +func boolPtr(b bool) *bool { + return &b +} diff --git a/app/router/api.go b/app/router/api.go index c1fe6ea..64f9981 100644 --- a/app/router/api.go +++ b/app/router/api.go @@ -14,6 +14,7 @@ import ( "web-medols-be/app/module/article_files" "web-medols-be/app/module/article_nulis_ai" "web-medols-be/app/module/articles" + "web-medols-be/app/module/bookmarks" "web-medols-be/app/module/cities" "web-medols-be/app/module/client_approval_settings" "web-medols-be/app/module/clients" @@ -54,6 +55,7 @@ type Router struct { ArticleApprovalsRouter *article_approvals.ArticleApprovalsRouter ArticlesRouter *articles.ArticlesRouter ArticleNulisAIRouter *article_nulis_ai.ArticleNulisAIRouter + BookmarksRouter *bookmarks.BookmarksRouter CitiesRouter *cities.CitiesRouter ClientApprovalSettingsRouter *client_approval_settings.ClientApprovalSettingsRouter ClientsRouter *clients.ClientsRouter @@ -89,6 +91,7 @@ func NewRouter( articleApprovalsRouter *article_approvals.ArticleApprovalsRouter, articlesRouter *articles.ArticlesRouter, articleNulisRouter *article_nulis_ai.ArticleNulisAIRouter, + bookmarksRouter *bookmarks.BookmarksRouter, citiesRouter *cities.CitiesRouter, clientApprovalSettingsRouter *client_approval_settings.ClientApprovalSettingsRouter, clientsRouter *clients.ClientsRouter, @@ -122,6 +125,7 @@ func NewRouter( ArticleApprovalsRouter: articleApprovalsRouter, ArticlesRouter: articlesRouter, ArticleNulisAIRouter: articleNulisRouter, + BookmarksRouter: bookmarksRouter, CitiesRouter: citiesRouter, ClientApprovalSettingsRouter: clientApprovalSettingsRouter, ClientsRouter: clientsRouter, @@ -165,6 +169,7 @@ func (r *Router) Register() { r.ArticlesRouter.RegisterArticlesRoutes() r.ArticleCommentsRouter.RegisterArticleCommentsRoutes() r.ArticleNulisAIRouter.RegisterArticleNulisAIRoutes() + r.BookmarksRouter.RegisterBookmarksRoutes() r.CitiesRouter.RegisterCitiesRoutes() r.ClientApprovalSettingsRouter.RegisterClientApprovalSettingsRoutes() r.ClientsRouter.RegisterClientsRoutes() diff --git a/config/toml/config.toml b/config/toml/config.toml index df4133f..2069708 100644 --- a/config/toml/config.toml +++ b/config/toml/config.toml @@ -14,7 +14,7 @@ body-limit = 1048576000 # "100 * 1024 * 1024" [db.postgres] dsn = "postgresql://medols_user:MedolsDB@2025@38.47.180.165:5432/medols_db" # ://:@:/ log-mode = "ERROR" -migrate = false +migrate = true seed = false [logger] diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index 037a115..c81cfe4 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -7943,6 +7943,524 @@ const docTemplate = `{ } } }, + "/bookmarks": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Bookmarks", + "tags": [ + "Bookmarks" + ], + "summary": "Get all Bookmarks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating new Bookmark", + "tags": [ + "Bookmarks" + ], + "summary": "Create new Bookmark", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Bookmark data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.BookmarksCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/bookmarks/summary": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting bookmark summary including total count and recent bookmarks", + "tags": [ + "Bookmarks" + ], + "summary": "Get Bookmark Summary for User", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/bookmarks/toggle/{articleId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for toggling bookmark status for an article", + "tags": [ + "Bookmarks" + ], + "summary": "Toggle Bookmark (Add/Remove)", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Article ID", + "name": "articleId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/bookmarks/user": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting Bookmarks by User ID", + "tags": [ + "Bookmarks" + ], + "summary": "Get Bookmarks by User ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/bookmarks/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting Bookmark by ID", + "tags": [ + "Bookmarks" + ], + "summary": "Get Bookmark by ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Bookmark ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting Bookmark", + "tags": [ + "Bookmarks" + ], + "summary": "Delete Bookmark", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Bookmark ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, "/cities": { "get": { "security": [ @@ -15738,6 +16256,17 @@ const docTemplate = `{ } } }, + "request.BookmarksCreateRequest": { + "type": "object", + "required": [ + "articleId" + ], + "properties": { + "articleId": { + "type": "integer" + } + } + }, "request.BulkCreateApprovalWorkflowStepsRequest": { "type": "object", "required": [ diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index 66516eb..3686b29 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -7932,6 +7932,524 @@ } } }, + "/bookmarks": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Bookmarks", + "tags": [ + "Bookmarks" + ], + "summary": "Get all Bookmarks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating new Bookmark", + "tags": [ + "Bookmarks" + ], + "summary": "Create new Bookmark", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Bookmark data", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.BookmarksCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/bookmarks/summary": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting bookmark summary including total count and recent bookmarks", + "tags": [ + "Bookmarks" + ], + "summary": "Get Bookmark Summary for User", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/bookmarks/toggle/{articleId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for toggling bookmark status for an article", + "tags": [ + "Bookmarks" + ], + "summary": "Toggle Bookmark (Add/Remove)", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Article ID", + "name": "articleId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/bookmarks/user": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting Bookmarks by User ID", + "tags": [ + "Bookmarks" + ], + "summary": "Get Bookmarks by User ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/bookmarks/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting Bookmark by ID", + "tags": [ + "Bookmarks" + ], + "summary": "Get Bookmark by ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Bookmark ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting Bookmark", + "tags": [ + "Bookmarks" + ], + "summary": "Delete Bookmark", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Bookmark ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, "/cities": { "get": { "security": [ @@ -15727,6 +16245,17 @@ } } }, + "request.BookmarksCreateRequest": { + "type": "object", + "required": [ + "articleId" + ], + "properties": { + "articleId": { + "type": "integer" + } + } + }, "request.BulkCreateApprovalWorkflowStepsRequest": { "type": "object", "required": [ diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 75e5267..ac69bef 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -529,6 +529,13 @@ definitions: - title - typeId type: object + request.BookmarksCreateRequest: + properties: + articleId: + type: integer + required: + - articleId + type: object request.BulkCreateApprovalWorkflowStepsRequest: properties: steps: @@ -6452,6 +6459,338 @@ paths: summary: Get articles waiting for approval by current user level tags: - Articles + /bookmarks: + get: + description: API for getting all Bookmarks + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - in: query + name: articleId + type: integer + - in: query + name: userId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all Bookmarks + tags: + - Bookmarks + post: + description: API for creating new Bookmark + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Bookmark data + in: body + name: req + required: true + schema: + $ref: '#/definitions/request.BookmarksCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create new Bookmark + tags: + - Bookmarks + /bookmarks/{id}: + delete: + description: API for deleting Bookmark + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Bookmark ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete Bookmark + tags: + - Bookmarks + get: + description: API for getting Bookmark by ID + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Bookmark ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get Bookmark by ID + tags: + - Bookmarks + /bookmarks/summary: + get: + description: API for getting bookmark summary including total count and recent + bookmarks + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get Bookmark Summary for User + tags: + - Bookmarks + /bookmarks/toggle/{articleId}: + post: + description: API for toggling bookmark status for an article + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Article ID + in: path + name: articleId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Toggle Bookmark (Add/Remove) + tags: + - Bookmarks + /bookmarks/user: + get: + description: API for getting Bookmarks by User ID + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - in: query + name: articleId + type: integer + - in: query + name: userId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get Bookmarks by User ID + tags: + - Bookmarks /cities: get: description: API for getting all Cities diff --git a/main.go b/main.go index 96d7330..1887187 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "web-medols-be/app/module/article_files" "web-medols-be/app/module/article_nulis_ai" "web-medols-be/app/module/articles" + "web-medols-be/app/module/bookmarks" "web-medols-be/app/module/cities" "web-medols-be/app/module/client_approval_settings" "web-medols-be/app/module/clients" @@ -80,6 +81,7 @@ func main() { articles.NewArticlesModule, article_comments.NewArticleCommentsModule, article_nulis_ai.NewArticleNulisAIModule, + bookmarks.NewBookmarksModule, cities.NewCitiesModule, client_approval_settings.NewClientApprovalSettingsModule, clients.NewClientsModule,