feat: add bookmarks

This commit is contained in:
hanif salafi 2025-09-17 01:21:27 +07:00
parent a4c83f8f0c
commit faecc65a46
16 changed files with 2463 additions and 3 deletions

View File

@ -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"`
}

View File

@ -98,6 +98,7 @@ func Models() []interface{} {
entity.ArticleComments{}, entity.ArticleComments{},
entity.ArticleNulisAI{}, entity.ArticleNulisAI{},
entity.AuditTrails{}, entity.AuditTrails{},
entity.Bookmarks{},
entity.Cities{}, entity.Cities{},
entity.Clients{}, entity.Clients{},
entity.ClientApprovalSettings{}, entity.ClientApprovalSettings{},

View File

@ -1,11 +1,12 @@
package activity_logs package activity_logs
import ( 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/controller"
"web-medols-be/app/module/activity_logs/repository" "web-medols-be/app/module/activity_logs/repository"
"web-medols-be/app/module/activity_logs/service" "web-medols-be/app/module/activity_logs/service"
"github.com/gofiber/fiber/v2"
"go.uber.org/fx"
) )
// struct of ActivityLogsRouter // struct of ActivityLogsRouter

View File

@ -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)
})
}

View File

@ -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 <Add access token here>)
// @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 <Add access token here>)
// @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 <Add access token here>)
// @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 <Add access token here>)
// @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 <Add access token here>)
// @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 <Add access token here>)
// @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 <Add access token here>)
// @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,
})
}

View File

@ -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 ""
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -14,6 +14,7 @@ import (
"web-medols-be/app/module/article_files" "web-medols-be/app/module/article_files"
"web-medols-be/app/module/article_nulis_ai" "web-medols-be/app/module/article_nulis_ai"
"web-medols-be/app/module/articles" "web-medols-be/app/module/articles"
"web-medols-be/app/module/bookmarks"
"web-medols-be/app/module/cities" "web-medols-be/app/module/cities"
"web-medols-be/app/module/client_approval_settings" "web-medols-be/app/module/client_approval_settings"
"web-medols-be/app/module/clients" "web-medols-be/app/module/clients"
@ -54,6 +55,7 @@ type Router struct {
ArticleApprovalsRouter *article_approvals.ArticleApprovalsRouter ArticleApprovalsRouter *article_approvals.ArticleApprovalsRouter
ArticlesRouter *articles.ArticlesRouter ArticlesRouter *articles.ArticlesRouter
ArticleNulisAIRouter *article_nulis_ai.ArticleNulisAIRouter ArticleNulisAIRouter *article_nulis_ai.ArticleNulisAIRouter
BookmarksRouter *bookmarks.BookmarksRouter
CitiesRouter *cities.CitiesRouter CitiesRouter *cities.CitiesRouter
ClientApprovalSettingsRouter *client_approval_settings.ClientApprovalSettingsRouter ClientApprovalSettingsRouter *client_approval_settings.ClientApprovalSettingsRouter
ClientsRouter *clients.ClientsRouter ClientsRouter *clients.ClientsRouter
@ -89,6 +91,7 @@ func NewRouter(
articleApprovalsRouter *article_approvals.ArticleApprovalsRouter, articleApprovalsRouter *article_approvals.ArticleApprovalsRouter,
articlesRouter *articles.ArticlesRouter, articlesRouter *articles.ArticlesRouter,
articleNulisRouter *article_nulis_ai.ArticleNulisAIRouter, articleNulisRouter *article_nulis_ai.ArticleNulisAIRouter,
bookmarksRouter *bookmarks.BookmarksRouter,
citiesRouter *cities.CitiesRouter, citiesRouter *cities.CitiesRouter,
clientApprovalSettingsRouter *client_approval_settings.ClientApprovalSettingsRouter, clientApprovalSettingsRouter *client_approval_settings.ClientApprovalSettingsRouter,
clientsRouter *clients.ClientsRouter, clientsRouter *clients.ClientsRouter,
@ -122,6 +125,7 @@ func NewRouter(
ArticleApprovalsRouter: articleApprovalsRouter, ArticleApprovalsRouter: articleApprovalsRouter,
ArticlesRouter: articlesRouter, ArticlesRouter: articlesRouter,
ArticleNulisAIRouter: articleNulisRouter, ArticleNulisAIRouter: articleNulisRouter,
BookmarksRouter: bookmarksRouter,
CitiesRouter: citiesRouter, CitiesRouter: citiesRouter,
ClientApprovalSettingsRouter: clientApprovalSettingsRouter, ClientApprovalSettingsRouter: clientApprovalSettingsRouter,
ClientsRouter: clientsRouter, ClientsRouter: clientsRouter,
@ -165,6 +169,7 @@ func (r *Router) Register() {
r.ArticlesRouter.RegisterArticlesRoutes() r.ArticlesRouter.RegisterArticlesRoutes()
r.ArticleCommentsRouter.RegisterArticleCommentsRoutes() r.ArticleCommentsRouter.RegisterArticleCommentsRoutes()
r.ArticleNulisAIRouter.RegisterArticleNulisAIRoutes() r.ArticleNulisAIRouter.RegisterArticleNulisAIRoutes()
r.BookmarksRouter.RegisterBookmarksRoutes()
r.CitiesRouter.RegisterCitiesRoutes() r.CitiesRouter.RegisterCitiesRoutes()
r.ClientApprovalSettingsRouter.RegisterClientApprovalSettingsRoutes() r.ClientApprovalSettingsRouter.RegisterClientApprovalSettingsRoutes()
r.ClientsRouter.RegisterClientsRoutes() r.ClientsRouter.RegisterClientsRoutes()

View File

@ -14,7 +14,7 @@ body-limit = 1048576000 # "100 * 1024 * 1024"
[db.postgres] [db.postgres]
dsn = "postgresql://medols_user:MedolsDB@2025@38.47.180.165:5432/medols_db" # <driver>://<username>:<password>@<host>:<port>/<database> dsn = "postgresql://medols_user:MedolsDB@2025@38.47.180.165:5432/medols_db" # <driver>://<username>:<password>@<host>:<port>/<database>
log-mode = "ERROR" log-mode = "ERROR"
migrate = false migrate = true
seed = false seed = false
[logger] [logger]

View File

@ -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": { "/cities": {
"get": { "get": {
"security": [ "security": [
@ -15738,6 +16256,17 @@ const docTemplate = `{
} }
} }
}, },
"request.BookmarksCreateRequest": {
"type": "object",
"required": [
"articleId"
],
"properties": {
"articleId": {
"type": "integer"
}
}
},
"request.BulkCreateApprovalWorkflowStepsRequest": { "request.BulkCreateApprovalWorkflowStepsRequest": {
"type": "object", "type": "object",
"required": [ "required": [

View File

@ -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": { "/cities": {
"get": { "get": {
"security": [ "security": [
@ -15727,6 +16245,17 @@
} }
} }
}, },
"request.BookmarksCreateRequest": {
"type": "object",
"required": [
"articleId"
],
"properties": {
"articleId": {
"type": "integer"
}
}
},
"request.BulkCreateApprovalWorkflowStepsRequest": { "request.BulkCreateApprovalWorkflowStepsRequest": {
"type": "object", "type": "object",
"required": [ "required": [

View File

@ -529,6 +529,13 @@ definitions:
- title - title
- typeId - typeId
type: object type: object
request.BookmarksCreateRequest:
properties:
articleId:
type: integer
required:
- articleId
type: object
request.BulkCreateApprovalWorkflowStepsRequest: request.BulkCreateApprovalWorkflowStepsRequest:
properties: properties:
steps: steps:
@ -6452,6 +6459,338 @@ paths:
summary: Get articles waiting for approval by current user level summary: Get articles waiting for approval by current user level
tags: tags:
- Articles - 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 <Add access token here>
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 <Add access token here>
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 <Add access token here>
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 <Add access token here>
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 <Add access token here>
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 <Add access token here>
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 <Add access token here>
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: /cities:
get: get:
description: API for getting all Cities description: API for getting all Cities

View File

@ -17,6 +17,7 @@ import (
"web-medols-be/app/module/article_files" "web-medols-be/app/module/article_files"
"web-medols-be/app/module/article_nulis_ai" "web-medols-be/app/module/article_nulis_ai"
"web-medols-be/app/module/articles" "web-medols-be/app/module/articles"
"web-medols-be/app/module/bookmarks"
"web-medols-be/app/module/cities" "web-medols-be/app/module/cities"
"web-medols-be/app/module/client_approval_settings" "web-medols-be/app/module/client_approval_settings"
"web-medols-be/app/module/clients" "web-medols-be/app/module/clients"
@ -80,6 +81,7 @@ func main() {
articles.NewArticlesModule, articles.NewArticlesModule,
article_comments.NewArticleCommentsModule, article_comments.NewArticleCommentsModule,
article_nulis_ai.NewArticleNulisAIModule, article_nulis_ai.NewArticleNulisAIModule,
bookmarks.NewBookmarksModule,
cities.NewCitiesModule, cities.NewCitiesModule,
client_approval_settings.NewClientApprovalSettingsModule, client_approval_settings.NewClientApprovalSettingsModule,
clients.NewClientsModule, clients.NewClientsModule,