package report import ( "context" "encoding/json" "errors" "github.com/google/uuid" "github.com/yuxingu/digital-psychology/apps/api/internal/model" "github.com/yuxingu/digital-psychology/apps/api/internal/portrait" "github.com/yuxingu/digital-psychology/apps/api/internal/repository" ) // Service creates and reads growth reports with entitlement trimming. type Service struct { Profiles *repository.ProfileRepo Reports *repository.ReportRepo } // CreatePortrait builds and stores a portrait report. func (s *Service) CreatePortrait(ctx context.Context, userID, profileID uuid.UUID) (*model.GrowthReport, error) { p, err := s.Profiles.GetForUser(ctx, userID, profileID) if err != nil { return nil, errors.New("profile not found") } out := portrait.Build(p.BirthDate, p.DisplayName) sum, _ := json.Marshal(out.Summary) det, _ := json.Marshal(out.Detail) rep, err := s.Reports.Create(ctx, userID, profileID, "portrait", sum, det) if err != nil { return nil, err } return s.applyEntitlement(ctx, userID, rep) } // Get returns a report with detail gated. func (s *Service) Get(ctx context.Context, userID, reportID uuid.UUID) (*model.GrowthReport, error) { rep, err := s.Reports.GetForUser(ctx, userID, reportID) if err != nil { return nil, errors.New("report not found") } return s.applyEntitlement(ctx, userID, rep) } func (s *Service) applyEntitlement(ctx context.Context, userID uuid.UUID, rep *model.GrowthReport) (*model.GrowthReport, error) { deep, err := s.Reports.HasDeepAccess(ctx, userID, rep.ID) if err != nil { return nil, err } vip, err := s.Reports.HasActiveMembership(ctx, userID) if err != nil { return nil, err } rep.HasDeep = deep || vip if !rep.HasDeep { rep.Detail = nil } return rep, nil } // CreateOrderInput for commerce. type CreateOrderInput struct { Kind string Plan string ReportID *uuid.UUID } // CreateOrder starts membership or deep_access order. func (s *Service) CreateOrder(ctx context.Context, userID uuid.UUID, in CreateOrderInput) (uuid.UUID, error) { if in.Kind != "membership" && in.Kind != "deep_access" { return uuid.Nil, errors.New("invalid kind") } if in.Kind == "deep_access" && in.ReportID == nil { return uuid.Nil, errors.New("report_id required") } amount := 990 if in.Kind == "membership" { amount = 2500 } return s.Reports.CreateOrder(ctx, userID, in.Kind, in.Plan, in.ReportID, amount) } // PayMock completes mock payment. func (s *Service) PayMock(ctx context.Context, userID, orderID uuid.UUID) error { return s.Reports.PayMock(ctx, userID, orderID) }