initial commit
This commit is contained in:
349
internal/application/asset/service.go
Normal file
349
internal/application/asset/service.go
Normal file
@@ -0,0 +1,349 @@
|
||||
package asset
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"go.uber.org/fx"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
domainAsset "base/internal/domain/asset"
|
||||
"base/internal/dto"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAssetNotFound = errors.New("asset not found")
|
||||
ErrCategoryNotFound = errors.New("asset category not found")
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
Create(ctx context.Context, req dto.CreateAssetRequest) (*dto.AssetResponse, error)
|
||||
GetByID(ctx context.Context, id uuid.UUID) (*dto.AssetResponse, error)
|
||||
Update(ctx context.Context, req dto.UpdateAssetRequest) (*dto.AssetResponse, error)
|
||||
Delete(ctx context.Context, id uuid.UUID) error
|
||||
FindByProfileID(ctx context.Context, profileID uuid.UUID) (*dto.ListAssetsResponse, error)
|
||||
ListCategories(ctx context.Context) (*dto.ListCategoriesResponse, error)
|
||||
ListByCategoryID(ctx context.Context, categoryID uuid.UUID, limit, page int) (*dto.ListAssetsByCategoryIDResponse, error)
|
||||
GetCategoriesWithPreview(ctx context.Context, req dto.CategoriesPreviewRequest) (*dto.CategoriesPreviewResponse, error)
|
||||
}
|
||||
|
||||
type service struct {
|
||||
logger zerolog.Logger
|
||||
assetRepo domainAsset.AssetRepository
|
||||
categoryRepo domainAsset.CategoryRepository
|
||||
}
|
||||
|
||||
type Param struct {
|
||||
Logger zerolog.Logger
|
||||
AssetRepo domainAsset.AssetRepository
|
||||
CategoryRepo domainAsset.CategoryRepository
|
||||
|
||||
fx.In
|
||||
}
|
||||
|
||||
func New(param Param) Service {
|
||||
return &service{
|
||||
logger: param.Logger,
|
||||
assetRepo: param.AssetRepo,
|
||||
categoryRepo: param.CategoryRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Create(ctx context.Context, req dto.CreateAssetRequest) (*dto.AssetResponse, error) {
|
||||
profileID, err := uuid.Parse(req.ProfileID)
|
||||
if err != nil {
|
||||
return nil, ErrAssetNotFound
|
||||
}
|
||||
categoryID, err := uuid.Parse(req.AssetCategoryID)
|
||||
if err != nil {
|
||||
return nil, ErrCategoryNotFound
|
||||
}
|
||||
|
||||
// Verify category exists
|
||||
category, err := s.categoryRepo.FindByID(ctx, categoryID)
|
||||
if err != nil || category == nil {
|
||||
return nil, ErrCategoryNotFound
|
||||
}
|
||||
|
||||
asset := &domainAsset.Asset{
|
||||
ID: uuid.New(),
|
||||
ProfileID: profileID,
|
||||
AssetCategoryID: categoryID,
|
||||
AssetCategory: *category,
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
Link: req.Link,
|
||||
Status: domainAsset.StatusPublished,
|
||||
}
|
||||
|
||||
if err := s.assetRepo.Create(ctx, asset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.toAssetResponse(asset), nil
|
||||
}
|
||||
|
||||
func (s *service) GetByID(ctx context.Context, id uuid.UUID) (*dto.AssetResponse, error) {
|
||||
asset, err := s.assetRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, ErrAssetNotFound
|
||||
}
|
||||
return s.toAssetResponse(asset), nil
|
||||
}
|
||||
|
||||
func (s *service) Update(ctx context.Context, req dto.UpdateAssetRequest) (*dto.AssetResponse, error) {
|
||||
id, err := uuid.Parse(req.ID)
|
||||
if err != nil {
|
||||
return nil, ErrAssetNotFound
|
||||
}
|
||||
|
||||
asset, err := s.assetRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, ErrAssetNotFound
|
||||
}
|
||||
|
||||
asset.Title = req.Title
|
||||
asset.Description = req.Description
|
||||
asset.Link = req.Link
|
||||
|
||||
if req.AssetCategoryID != "" {
|
||||
categoryID, err := uuid.Parse(req.AssetCategoryID)
|
||||
if err == nil {
|
||||
category, err := s.categoryRepo.FindByID(ctx, categoryID)
|
||||
if err == nil && category != nil {
|
||||
asset.AssetCategoryID = categoryID
|
||||
asset.AssetCategory = *category
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if req.Status != nil && *req.Status >= 0 && *req.Status <= 3 {
|
||||
asset.Status = domainAsset.Status(*req.Status)
|
||||
}
|
||||
|
||||
if err := s.assetRepo.Update(ctx, asset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.toAssetResponse(asset), nil
|
||||
}
|
||||
|
||||
func (s *service) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
asset, err := s.assetRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return ErrAssetNotFound
|
||||
}
|
||||
return s.assetRepo.Delete(ctx, asset)
|
||||
}
|
||||
|
||||
func (s *service) FindByProfileID(ctx context.Context, profileID uuid.UUID) (*dto.ListAssetsResponse, error) {
|
||||
assets, err := s.assetRepo.FindByProfileID(ctx, profileID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := &dto.ListAssetsResponse{
|
||||
Assets: make([]dto.AssetResponse, len(assets)),
|
||||
}
|
||||
for i, a := range assets {
|
||||
resp.Assets[i] = *s.toAssetResponse(a)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *service) ListCategories(ctx context.Context) (*dto.ListCategoriesResponse, error) {
|
||||
categories, err := s.categoryRepo.FindAll(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := &dto.ListCategoriesResponse{
|
||||
Categories: make([]dto.CategoryDTO, len(categories)),
|
||||
}
|
||||
for i, c := range categories {
|
||||
resp.Categories[i] = dto.CategoryDTO{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Icon: c.Icon,
|
||||
Color: c.Color,
|
||||
CardType: c.CardType,
|
||||
Featured: c.Featured,
|
||||
Description: c.Description,
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *service) ListByCategoryID(ctx context.Context, categoryID uuid.UUID, limit, page int) (*dto.ListAssetsByCategoryIDResponse, error) {
|
||||
if limit < 1 {
|
||||
limit = 10
|
||||
}
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
category, err := s.categoryRepo.FindByID(ctx, categoryID)
|
||||
if err != nil || category == nil {
|
||||
return nil, ErrCategoryNotFound
|
||||
}
|
||||
|
||||
total, err := s.assetRepo.CountByCategory(ctx, categoryID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offset := (page - 1) * limit
|
||||
assets, err := s.assetRepo.FindLatestByCategoryPaginated(ctx, categoryID, limit, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalPages := (total + limit - 1) / limit
|
||||
if totalPages < 1 {
|
||||
totalPages = 1
|
||||
}
|
||||
|
||||
resp := &dto.ListAssetsByCategoryIDResponse{
|
||||
Category: dto.CategoryDTO{
|
||||
ID: category.ID,
|
||||
Name: category.Name,
|
||||
Icon: category.Icon,
|
||||
Color: category.Color,
|
||||
CardType: category.CardType,
|
||||
Featured: category.Featured,
|
||||
Description: category.Description,
|
||||
},
|
||||
Assets: make([]dto.AssetResponse, len(assets)),
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: limit,
|
||||
TotalPages: totalPages,
|
||||
}
|
||||
for i, a := range assets {
|
||||
resp.Assets[i] = *s.toAssetResponse(a)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *service) GetCategoriesWithPreview(ctx context.Context, req dto.CategoriesPreviewRequest) (*dto.CategoriesPreviewResponse, error) {
|
||||
perCategory := req.AssetsPerCategory
|
||||
if perCategory < 1 {
|
||||
perCategory = 8
|
||||
}
|
||||
if perCategory > 20 {
|
||||
perCategory = 20
|
||||
}
|
||||
|
||||
var categoryIDs []uuid.UUID
|
||||
for _, s := range req.CategoryIDs {
|
||||
if id, err := uuid.Parse(s); err == nil {
|
||||
categoryIDs = append(categoryIDs, id)
|
||||
}
|
||||
}
|
||||
|
||||
var categories []*domainAsset.Category
|
||||
var err error
|
||||
if len(categoryIDs) > 0 {
|
||||
categories, err = s.categoryRepo.FindByIDs(ctx, categoryIDs)
|
||||
} else {
|
||||
categories, err = s.categoryRepo.FindAll(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.FeaturedOnly {
|
||||
filtered := make([]*domainAsset.Category, 0, len(categories))
|
||||
for _, c := range categories {
|
||||
if c.Featured {
|
||||
filtered = append(filtered, c)
|
||||
}
|
||||
}
|
||||
categories = filtered
|
||||
}
|
||||
|
||||
results := make([]dto.CategoryWithPreviewAssetsDTO, len(categories))
|
||||
g, gCtx := errgroup.WithContext(ctx)
|
||||
|
||||
for index, category := range categories {
|
||||
i, cat := index, category
|
||||
g.Go(func() error {
|
||||
assets, assetErr := s.assetRepo.FindLatestByCategory(gCtx, cat.ID, perCategory)
|
||||
if assetErr != nil {
|
||||
return assetErr
|
||||
}
|
||||
|
||||
total, _ := s.assetRepo.CountByCategory(gCtx, cat.ID)
|
||||
|
||||
assetResps := make([]dto.AssetResponse, len(assets))
|
||||
for j, a := range assets {
|
||||
assetResps[j] = *s.toAssetResponse(a)
|
||||
}
|
||||
|
||||
results[i] = dto.CategoryWithPreviewAssetsDTO{
|
||||
Category: dto.CategoryDTO{
|
||||
ID: cat.ID,
|
||||
Name: cat.Name,
|
||||
Icon: cat.Icon,
|
||||
Color: cat.Color,
|
||||
CardType: cat.CardType,
|
||||
Featured: cat.Featured,
|
||||
Description: cat.Description,
|
||||
},
|
||||
Assets: assetResps,
|
||||
TotalAssets: total,
|
||||
HasMore: total > perCategory,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dto.CategoriesPreviewResponse{Categories: results}, nil
|
||||
}
|
||||
|
||||
func (s *service) toAssetResponse(a *domainAsset.Asset) *dto.AssetResponse {
|
||||
coverImage := ""
|
||||
for _, art := range a.AssetArtifacts {
|
||||
if strings.Contains(strings.ToLower(art.Type), "image") {
|
||||
coverImage = art.DownloadURL
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
resp := &dto.AssetResponse{
|
||||
ID: a.ID,
|
||||
ProfileID: a.ProfileID,
|
||||
AssetCategoryID: a.AssetCategoryID,
|
||||
Title: a.Title,
|
||||
Description: a.Description,
|
||||
Link: a.Link,
|
||||
CoverImage: coverImage,
|
||||
Status: int(a.Status),
|
||||
CreatedAt: formatTime(a.CreatedAt),
|
||||
UpdatedAt: formatTime(a.UpdatedAt),
|
||||
}
|
||||
|
||||
resp.Category = dto.CategoryDTO{
|
||||
ID: a.AssetCategory.ID,
|
||||
Name: a.AssetCategory.Name,
|
||||
Icon: a.AssetCategory.Icon,
|
||||
Color: a.AssetCategory.Color,
|
||||
CardType: a.AssetCategory.CardType,
|
||||
Featured: a.AssetCategory.Featured,
|
||||
Description: a.AssetCategory.Description,
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func formatTime(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return t.Format(time.RFC3339)
|
||||
}
|
||||
Reference in New Issue
Block a user