239 lines
5.3 KiB
Go
239 lines
5.3 KiB
Go
package landing
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/rs/zerolog"
|
|
"go.uber.org/fx"
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
domainAsset "base/internal/domain/asset"
|
|
domainProfile "base/internal/domain/profile"
|
|
"base/internal/dto"
|
|
"base/pkg/cache"
|
|
)
|
|
|
|
const (
|
|
defaultAssetsPerCategory = 6
|
|
defaultSpecialistsLimit = 6
|
|
defaultRolesLimit = 20
|
|
landingCacheKey = "landing:page"
|
|
landingCacheTTL = 5 * time.Minute
|
|
)
|
|
|
|
type Service interface {
|
|
GetLanding(ctx context.Context) (*dto.Landing, error)
|
|
}
|
|
|
|
type service struct {
|
|
logger zerolog.Logger
|
|
cache cache.Cache[dto.Landing]
|
|
categoryRepo domainAsset.CategoryRepository
|
|
assetRepo domainAsset.AssetRepository
|
|
profileRepo domainProfile.Repository
|
|
roleRepo domainProfile.RoleRepository
|
|
}
|
|
|
|
type Param struct {
|
|
Logger zerolog.Logger
|
|
Cache cache.Cache[dto.Landing]
|
|
CategoryRepo domainAsset.CategoryRepository
|
|
AssetRepo domainAsset.AssetRepository
|
|
ProfileRepo domainProfile.Repository
|
|
RoleRepo domainProfile.RoleRepository
|
|
|
|
fx.In
|
|
}
|
|
|
|
func New(param Param) Service {
|
|
return &service{
|
|
logger: param.Logger,
|
|
cache: param.Cache,
|
|
categoryRepo: param.CategoryRepo,
|
|
assetRepo: param.AssetRepo,
|
|
profileRepo: param.ProfileRepo,
|
|
roleRepo: param.RoleRepo,
|
|
}
|
|
}
|
|
|
|
func (s *service) GetLanding(ctx context.Context) (*dto.Landing, error) {
|
|
result, err := s.cache.WithCache(ctx, landingCacheKey, s.fetchLanding, landingCacheTTL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
func (s *service) fetchLanding(ctx context.Context) (dto.Landing, error) {
|
|
data := &dto.LandingPageData{
|
|
Categories: []dto.CategoryDTO{},
|
|
SpecialistRoles: []dto.ProfileRole{},
|
|
Assets: []dto.LandingAssetData{},
|
|
Specialists: []dto.Specialist{},
|
|
Blogs: []dto.Blog{},
|
|
}
|
|
|
|
g, gCtx := errgroup.WithContext(ctx)
|
|
|
|
g.Go(func() error {
|
|
categories, err := s.categoryRepo.FindAll(gCtx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data.Categories = make([]dto.CategoryDTO, len(categories))
|
|
for i, c := range categories {
|
|
data.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 nil
|
|
})
|
|
|
|
g.Go(func() error {
|
|
domainRoles, err := s.roleRepo.List(gCtx, defaultRolesLimit, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data.SpecialistRoles = make([]dto.ProfileRole, len(domainRoles))
|
|
for i, r := range domainRoles {
|
|
data.SpecialistRoles[i] = dto.ProfileRole{
|
|
Id: r.ID.String(),
|
|
Title: r.Title,
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
g.Go(func() error {
|
|
profiles, _, err := s.profileRepo.FindAll(
|
|
gCtx,
|
|
domainProfile.Filter{
|
|
Page: 1,
|
|
PageSize: defaultSpecialistsLimit,
|
|
SortedBy: "created_at",
|
|
Ascending: false,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data.Specialists = make([]dto.Specialist, len(profiles))
|
|
for i, p := range profiles {
|
|
data.Specialists[i] = dto.Specialist{
|
|
Id: p.ID.String(),
|
|
Handle: p.Handle,
|
|
Avatar: p.About.ProfilePicture,
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
g.Go(func() error {
|
|
categories, err := s.categoryRepo.FindAll(gCtx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
assetsByCat := make([]dto.LandingAssetData, len(categories))
|
|
mu := &sync.Mutex{}
|
|
eg, egCtx := errgroup.WithContext(gCtx)
|
|
|
|
for index, category := range categories {
|
|
i, cat := index, category
|
|
|
|
eg.Go(func() error {
|
|
assets, findLatestAssetErr := s.assetRepo.FindLatestByCategory(egCtx, cat.ID, defaultAssetsPerCategory)
|
|
if findLatestAssetErr != nil {
|
|
return findLatestAssetErr
|
|
}
|
|
|
|
assetResp := make([]dto.AssetResponse, len(assets))
|
|
for j, a := range assets {
|
|
assetResp[j] = *s.toAssetResponse(a)
|
|
}
|
|
|
|
mu.Lock()
|
|
|
|
assetsByCat[i] = dto.LandingAssetData{
|
|
AssetCategory: dto.AssetCategory{
|
|
Id: cat.ID.String(),
|
|
Title: cat.Name,
|
|
Icon: cat.Icon,
|
|
},
|
|
Assets: assetResp,
|
|
}
|
|
|
|
mu.Unlock()
|
|
return nil
|
|
})
|
|
}
|
|
|
|
if err = eg.Wait(); err != nil {
|
|
return err
|
|
}
|
|
data.Assets = assetsByCat
|
|
return nil
|
|
})
|
|
|
|
if err := g.Wait(); err != nil {
|
|
return dto.Landing{}, err
|
|
}
|
|
|
|
return dto.Landing{
|
|
Message: "Landing page fetched successfully",
|
|
Data: *data,
|
|
}, 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),
|
|
}
|
|
if a.AssetCategory.ID != uuid.Nil {
|
|
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)
|
|
}
|