Files
base/internal/application/discovery/service.go
2026-04-10 18:25:21 +03:30

273 lines
6.7 KiB
Go

package discovery
import (
"context"
"strings"
"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"
)
type Service interface {
GetDiscoveryOverview(ctx context.Context) (*dto.OverviewFetchedResponse, error)
}
type service struct {
logger zerolog.Logger
profileRepo domainProfile.Repository
assetRepo domainAsset.AssetRepository
categoryRepo domainAsset.CategoryRepository
}
// Param holds dependencies for the discovery overview service.
type Param struct {
Logger zerolog.Logger
ProfileRepo domainProfile.Repository
AssetRepo domainAsset.AssetRepository
CategoryRepo domainAsset.CategoryRepository
fx.In
}
func New(param Param) Service {
return &service{
logger: param.Logger,
profileRepo: param.ProfileRepo,
assetRepo: param.AssetRepo,
categoryRepo: param.CategoryRepo,
}
}
func (s *service) GetDiscoveryOverview(ctx context.Context) (*dto.OverviewFetchedResponse, error) {
resp := &struct {
domainAssets []*domainAsset.Asset
recentlyJoined []*domainProfile.Profile
totalProfiles int
totalAssets int
}{}
g, gCtx := errgroup.WithContext(ctx)
g.Go(func() error {
assets, err := s.assetRepo.FindLatest(gCtx, 6, 0)
if err != nil {
return err
}
resp.domainAssets = assets
return nil
})
g.Go(func() error {
profiles, total, err := s.profileRepo.FindAll(gCtx, domainProfile.Filter{
Page: 1,
PageSize: 6,
SortedBy: "created_at",
Ascending: false,
})
if err != nil {
return err
}
resp.recentlyJoined = profiles
resp.totalProfiles = total
return nil
})
g.Go(func() error {
count, err := s.assetRepo.Count(gCtx)
if err != nil {
return err
}
resp.totalAssets = count
return nil
})
if err := g.Wait(); err != nil {
return nil, err
}
assets := s.toOverviewAssets(ctx, resp.domainAssets)
flatProfiles := ToFlatProfiles(resp.recentlyJoined)
return &dto.OverviewFetchedResponse{
Message: "Overview fetched successfully",
Data: dto.OverviewFetchedDataDTO{
Assets: assets,
RecentlyJoined: flatProfiles,
Analytics: dto.AnalyticsDTO{
TotalAssets: resp.totalAssets,
TotalProfiles: resp.totalProfiles,
},
},
}, nil
}
func (s *service) toOverviewAssets(ctx context.Context, assets []*domainAsset.Asset) []dto.OverviewAssetDTO {
out := make([]dto.OverviewAssetDTO, len(assets))
for i, a := range assets {
out[i] = s.toOverviewAsset(ctx, a)
}
return out
}
func (s *service) toOverviewAsset(ctx context.Context, a *domainAsset.Asset) dto.OverviewAssetDTO {
price := 0
coverImage := ""
for _, art := range a.AssetArtifacts {
if strings.Contains(strings.ToLower(art.Type), "image") {
coverImage = art.DownloadURL
break
}
}
if len(a.AssetArtifacts) > 0 {
price = a.AssetArtifacts[0].Price
}
cat := (*dto.CategoryDTO)(nil)
if a.AssetCategory.ID != uuid.Nil {
cat = &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,
}
}
ownerID := a.ProfileID.String()
if p, err := s.profileRepo.FindByID(ctx, a.ProfileID); err == nil && p.UserID != nil {
ownerID = p.UserID.String()
}
return dto.OverviewAssetDTO{
ID: a.ID.String(),
Title: a.Title,
Description: a.Description,
Content: a.Description,
AssetCategoryID: a.AssetCategoryID.String(),
AssetCategory: cat,
CoverImage: coverImage,
Link: a.Link,
OwnerID: ownerID,
ProfileID: a.ProfileID.String(),
Profile: nil,
Price: price,
Currency: "USD",
Status: assetStatusToString(a.Status),
Rating: 0,
CreatedAt: a.CreatedAt,
UpdatedAt: a.UpdatedAt,
}
}
func assetStatusToString(st domainAsset.Status) string {
switch st {
case domainAsset.StatusPublished:
return "published"
case domainAsset.StatusDisabled:
return "disabled"
case domainAsset.StatusPending:
return "pending"
case domainAsset.StatusDeleted:
return "deleted"
default:
return ""
}
}
func formatTime(t time.Time) string {
if t.IsZero() {
return ""
}
return t.Format(time.RFC3339)
}
// ToFlatProfiles converts domain profiles to flat DTOs.
func ToFlatProfiles(profiles []*domainProfile.Profile) []dto.FlatProfileDTO {
out := make([]dto.FlatProfileDTO, len(profiles))
for i, p := range profiles {
out[i] = ToFlatProfile(p)
}
return out
}
// ToFlatProfile converts a single profile to flat DTO.
func ToFlatProfile(p *domainProfile.Profile) dto.FlatProfileDTO {
roleID := ""
roleName := ""
if p.Hero.Role != nil {
roleID = p.Hero.Role.ID.String()
if p.Hero.Role.Title != "" {
roleName = p.Hero.Role.Title
}
}
achievements := make(map[string]dto.AchievementItemDTO)
for _, a := range p.About.Achievements {
key := strings.ToLower(strings.ReplaceAll(a.Title, " ", ""))
if key == "" {
key = a.Title
}
achievements[key] = dto.AchievementItemDTO{Value: a.Value, Enabled: a.Enabled}
}
if len(achievements) == 0 {
achievements = map[string]dto.AchievementItemDTO{
"happyClient": {Value: "", Enabled: true},
"yearExperience": {Value: "", Enabled: true},
"projectCompeleted": {Value: "", Enabled: true},
}
}
var socialLinks []dto.SocialLinkDTO
for _, sl := range p.Contact.SocialLinks {
socialLinks = append(socialLinks, dto.SocialLinkDTO{LinkType: sl.LinkType, Link: sl.Link})
}
displayName := strings.TrimSpace(p.Hero.FirstName + " " + p.Hero.LastName)
if displayName == "" {
displayName = p.Handle
}
status := "published"
if p.PageSetting.VisibilityLevel != "public" {
status = "draft"
}
return dto.FlatProfileDTO{
ID: p.ID.String(),
ProfileHandle: p.Handle,
Status: status,
BackgroundImage: "",
ProfilePicture: p.About.ProfilePicture,
FirstName: p.Hero.FirstName,
LastName: p.Hero.LastName,
DisplayName: displayName,
RoleID: roleID,
Role: dto.RoleDTO{ID: roleID, Name: roleName},
CurrentCompany: p.Hero.Company,
ShortDescription: p.Hero.ShortDescription,
CTAEnabled: p.Hero.CTAEnabled,
CTAAction: "",
ResumeLink: p.Hero.ResumeLink,
About: p.About.About,
ContactEmail: p.Contact.Email,
Achievements: achievements,
ContactPhone: p.Contact.Phone,
Country: "",
CustomRoles: "",
RoleLevel: p.Hero.Role.Level,
SocialLinks: socialLinks,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
HandleUpdatedAt: time.Time{},
}
}