package service import ( "errors" "netidhub-saas-be/app/database/entity" articlesRepository "netidhub-saas-be/app/module/articles/repository" "netidhub-saas-be/app/module/bookmarks/mapper" "netidhub-saas-be/app/module/bookmarks/repository" "netidhub-saas-be/app/module/bookmarks/request" "netidhub-saas-be/app/module/bookmarks/response" usersRepository "netidhub-saas-be/app/module/users/repository" "netidhub-saas-be/config/config" "netidhub-saas-be/utils/paginator" utilSvc "netidhub-saas-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 Cfg *config.Config } // BookmarksService define interface of IBookmarksService type BookmarksService interface { All(authToken string, req request.BookmarksQueryRequest) (bookmarks []*response.BookmarksResponse, paging paginator.Pagination, err error) Show(authToken string, id uint) (bookmark *response.BookmarksResponse, err error) Save(authToken string, req request.BookmarksCreateRequest) (bookmark *entity.Bookmarks, err error) Delete(authToken string, id uint) error GetByUserId(authToken string, req request.BookmarksQueryRequest) (bookmarks []*response.BookmarksResponse, paging paginator.Pagination, err error) ToggleBookmark(authToken string, articleId uint) (isBookmarked bool, err error) GetBookmarkSummary(authToken string) (summary *response.BookmarksSummaryResponse, err error) CheckBookmarkByArticleId(authToken string, articleId uint) (isBookmarked bool, bookmarkId *uint, err error) } // NewBookmarksService init BookmarksService func NewBookmarksService( repo repository.BookmarksRepository, articlesRepo articlesRepository.ArticlesRepository, usersRepo usersRepository.UsersRepository, cfg *config.Config, log zerolog.Logger, ) BookmarksService { return &bookmarksService{ Repo: repo, ArticlesRepo: articlesRepo, UsersRepo: usersRepo, Log: log, Cfg: cfg, } } // implement interface of IBookmarksService func (_i *bookmarksService) All(authToken string, req request.BookmarksQueryRequest) (bookmarks []*response.BookmarksResponse, paging paginator.Pagination, err error) { // Extract clientId from authToken var clientId *uuid.UUID if authToken != "" { user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if user != nil && user.ClientId != nil { clientId = user.ClientId req.UserId = &user.ID _i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token") } } 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) } domain := _i.Cfg.App.Domain bookmarks = mapper.ToBookmarksResponseList(domain, bookmarksSlice) return bookmarks, paging, nil } func (_i *bookmarksService) Show(authToken string, id uint) (bookmark *response.BookmarksResponse, err error) { // Extract clientId from authToken var clientId *uuid.UUID if authToken != "" { user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if user != nil && user.ClientId != nil { clientId = user.ClientId _i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token") } } bookmarkEntity, err := _i.Repo.FindOne(clientId, id) if err != nil { _i.Log.Error().Err(err).Msg("Failed to show bookmark") return nil, err } domain := _i.Cfg.App.Domain bookmark = mapper.ToBookmarksResponse(domain, bookmarkEntity) return bookmark, nil } func (_i *bookmarksService) Save(authToken string, req request.BookmarksCreateRequest) (bookmark *entity.Bookmarks, err error) { // Extract clientId from authToken var clientId *uuid.UUID if authToken != "" { user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if user != nil && user.ClientId != nil { clientId = user.ClientId _i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token") } } // 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(nil, 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(authToken string, id uint) error { // Extract clientId from authToken var clientId *uuid.UUID if authToken != "" { user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if user != nil && user.ClientId != nil { clientId = user.ClientId _i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token") } } err := _i.Repo.Delete(nil, id) if err != nil { _i.Log.Error().Err(err).Msg("Failed to delete bookmark") return err } return nil } func (_i *bookmarksService) GetByUserId(authToken string, req request.BookmarksQueryRequest) (bookmarks []*response.BookmarksResponse, paging paginator.Pagination, err error) { // Extract clientId from authToken var clientId *uuid.UUID if authToken != "" { user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if user != nil && user.ClientId != nil { clientId = user.ClientId _i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token") } } // 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) } domain := _i.Cfg.App.Domain bookmarks = mapper.ToBookmarksResponseList(domain, bookmarksSlice) return bookmarks, paging, nil } func (_i *bookmarksService) ToggleBookmark(authToken string, articleId uint) (isBookmarked bool, err error) { // Extract clientId from authToken var clientId *uuid.UUID if authToken != "" { user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if user != nil && user.ClientId != nil { clientId = user.ClientId _i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token") } } // 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(nil, 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(authToken string) (summary *response.BookmarksSummaryResponse, err error) { // Extract clientId from authToken var clientId *uuid.UUID if authToken != "" { user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if user != nil && user.ClientId != nil { clientId = user.ClientId _i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token") } } // 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) } domain := _i.Cfg.App.Domain recentBookmarks := mapper.ToBookmarksResponseList(domain, bookmarksSlice) summary = &response.BookmarksSummaryResponse{ TotalBookmarks: int(totalCount), RecentBookmarks: recentBookmarks, } return summary, nil } func (_i *bookmarksService) CheckBookmarkByArticleId(authToken string, articleId uint) (isBookmarked bool, bookmarkId *uint, err error) { // Extract clientId from authToken var clientId *uuid.UUID if authToken != "" { user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) if user != nil && user.ClientId != nil { clientId = user.ClientId _i.Log.Info().Interface("clientId", clientId).Msg("Extracted clientId from auth token") } } // 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, nil, errors.New("user not found") } // Check if article exists _, err = _i.ArticlesRepo.FindOne(nil, articleId) if err != nil { _i.Log.Error().Err(err).Msg("Article not found") return false, nil, errors.New("article not found") } // Check if bookmark exists existingBookmark, err := _i.Repo.FindByUserAndArticle(clientId, user.ID, articleId) if err != nil { // Bookmark doesn't exist return false, nil, nil } if existingBookmark != nil { return true, &existingBookmark.ID, nil } return false, nil, nil } // Helper function to create bool pointer func boolPtr(b bool) *bool { return &b }