package logic import ( "context" "strings" "spark-server/internal/module/client/entity" "spark-server/internal/pkg/errs" ) // DeveloperApps returns enabled apps for the given source app (client wall). func (s *Service) DeveloperApps(ctx context.Context, sourceAppCode, excludePackage string) (*entity.DeveloperAppsPayload, error) { sourceAppCode = normalizeAppCode(sourceAppCode) excludePackage = strings.TrimSpace(excludePackage) rows := s.loadDeveloperApps(ctx, sourceAppCode, true) // SleepHeart 可回退 spark 共用墙 if len(rows) == 0 && sourceAppCode == "sleepheart" { rows = s.loadDeveloperApps(ctx, "spark", true) } items := make([]entity.DeveloperAppItem, 0, len(rows)) for _, row := range rows { if excludePackage != "" && strings.EqualFold(row.TargetPackageName, excludePackage) { continue } items = append(items, toDeveloperAppItem(row)) } return &entity.DeveloperAppsPayload{Items: items}, nil } // ListDeveloperAppsAdmin returns all rows for popadmin (including disabled). func (s *Service) ListDeveloperAppsAdmin(ctx context.Context, sourceAppCode string) ([]entity.DeveloperApp, error) { return s.loadDeveloperApps(ctx, normalizeAppCode(sourceAppCode), false), nil } // CreateDeveloperAppAdmin inserts or updates one recommendation. func (s *Service) CreateDeveloperAppAdmin(ctx context.Context, in entity.DeveloperApp) (*entity.DeveloperApp, error) { if s.repo == nil { return nil, errs.ErrAvailable } in.SourceAppCode = normalizeAppCode(in.SourceAppCode) in.TargetAppName = strings.TrimSpace(in.TargetAppName) in.TargetPackageName = strings.TrimSpace(in.TargetPackageName) in.Subtitle = strings.TrimSpace(in.Subtitle) in.IconURL = strings.TrimSpace(in.IconURL) in.StoreURL = strings.TrimSpace(in.StoreURL) in.FallbackURL = strings.TrimSpace(in.FallbackURL) in.SchemaURL = strings.TrimSpace(in.SchemaURL) if in.TargetAppName == "" { return nil, errs.ErrInvalidArgument.WithMsg("targetAppName required") } if in.TargetPackageName == "" { in.TargetPackageName = "pkg_" + strings.ToLower(strings.ReplaceAll(in.TargetAppName, " ", "_")) } if in.StoreURL == "" && in.FallbackURL != "" { in.StoreURL = in.FallbackURL } in.Enabled = true if err := s.repo.UpsertDeveloperApp(ctx, &in); err != nil { return nil, err } return &in, nil } // DeleteDeveloperAppAdmin removes one row by id. func (s *Service) DeleteDeveloperAppAdmin(ctx context.Context, id int64) error { if s.repo == nil { return errs.ErrAvailable } if id <= 0 { return errs.ErrInvalidArgument } return s.repo.DeleteDeveloperApp(ctx, id) } // SyncWebsiteIconsAdmin upserts catalog apps + default icons from wyrs365.com. func (s *Service) SyncWebsiteIconsAdmin(ctx context.Context) (int, error) { if s.repo == nil { return 0, errs.ErrAvailable } n := 0 for _, src := range []string{"spark", "sleepheart"} { for _, row := range websiteDeveloperCatalog(src) { cur := row if err := s.repo.UpsertDeveloperApp(ctx, &cur); err != nil { return n, err } n++ } } return n, nil } func (s *Service) loadDeveloperApps(ctx context.Context, sourceAppCode string, enabledOnly bool) []entity.DeveloperApp { if s.repo != nil { rows, err := s.repo.ListDeveloperApps(ctx, sourceAppCode, enabledOnly) if err == nil && len(rows) > 0 { return rows } } return defaultDeveloperApps(sourceAppCode) } func toDeveloperAppItem(a entity.DeveloperApp) entity.DeveloperAppItem { store := a.StoreURL if store == "" { store = a.FallbackURL } return entity.DeveloperAppItem{ ID: a.ID, Name: a.TargetAppName, Subtitle: a.Subtitle, IconURL: a.IconURL, StoreURL: store, SchemaURL: a.SchemaURL, PackageName: a.TargetPackageName, SortOrder: a.SortOrder, } } func normalizeAppCode(code string) string { code = strings.ToLower(strings.TrimSpace(code)) switch code { case "", "pop": return "spark" case "health", "healthios", "sleep", "sleep-heart": return "sleepheart" default: return code } } func defaultDeveloperApps(sourceAppCode string) []entity.DeveloperApp { return websiteDeveloperCatalog(sourceAppCode) } func websiteDeveloperCatalog(sourceAppCode string) []entity.DeveloperApp { mk := func(name, pkg, sub, icon, page string, order int) entity.DeveloperApp { if icon == "" { icon = "https://www.wyrs365.com/assets/logo-piggy-account.png" } return entity.DeveloperApp{ SourceAppCode: sourceAppCode, TargetAppName: name, TargetPackageName: pkg, Subtitle: sub, IconURL: icon, StoreURL: page, FallbackURL: page, SortOrder: order, Enabled: true, } } return []entity.DeveloperApp{ mk("Flipclock桌面翻页时钟", "com.wyrs.flipclock", "桌面翻页时钟小组件", "https://www.wyrs365.com/assets/logo-flipclock.png", "https://www.wyrs365.com/flipclock", 10), mk("手指测心率", "com.wyrs.heartrate", "指尖 PPG 测心率", "https://www.wyrs365.com/assets/logo-heartrate.png", "https://www.wyrs365.com/heartrate", 20), mk("猪咪记账记事本", "com.wyrs.piggy", "记账 · 倒数日 · 小组件", "https://www.wyrs365.com/assets/logo-piggy-account.png", "https://www.wyrs365.com/piggy-account", 30), mk("鼾声分贝仪", "com.wyrs.snore", "鼾声检测与分贝测量", "https://www.wyrs365.com/assets/logo-piggy-account.png", "https://www.wyrs365.com/decibel", 40), mk("微雨LedBanner", "com.wyrs.ledbanner", "LED 滚动灯牌", "https://www.wyrs365.com/assets/logo-piggy-account.png", "https://www.wyrs365.com/led-banner", 50), mk("微雨PDF扫描转换器", "com.wyrs.wypdf", "PDF 扫描转换", "https://www.wyrs365.com/assets/logo-piggy-account.png", "https://www.wyrs365.com/pdf-scanner", 60), mk("RunSteps", "com.wyrs.runsteps", "步数与运动", "https://www.wyrs365.com/assets/logo-piggy-account.png", "https://www.wyrs365.com/runsteps", 70), mk("火花", "com.wyrs.spark", "即时通讯 · 火花", "https://www.wyrs365.com/assets/logo-piggy-account.png", "https://www.wyrs365.com/", 80), } }