diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..0aa860d --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(go build *)", + "Bash(go vet *)" + ] + } +} diff --git a/apps/catalog/domain/domain.go b/apps/catalog/domain/domain.go index 99f812c..38f6374 100755 --- a/apps/catalog/domain/domain.go +++ b/apps/catalog/domain/domain.go @@ -8,6 +8,7 @@ type Domain struct { SoftwareRepository SoftwareRepositoryInterface ReleaseRepository ReleaseRepositoryInterface FileRepository FileRepositoryInterface + ExternalLinkRepository ExternalLinkRepositoryInterface DownloadService DownloadServiceInterface SoftwareUpdaterService SoftwareUpdaterServiceInterface SoftwareService SoftwareServiceInterface @@ -21,6 +22,7 @@ func NewDomain() Domain { software_repository := &SoftwareRepository{db: DB} release_repository := &ReleaseRepository{db: DB} file_repository := NewFileRepository() + external_link_repository := &ExternalLinkRepository{db: DB} software_service := NewSoftwareService(software_repository, release_repository) @@ -28,18 +30,21 @@ func NewDomain() Domain { software_repository, release_repository, file_repository, + external_link_repository, ) ebitengine_updater := NewSoftwareUpdaterEbitengineService( software_repository, release_repository, file_repository, + external_link_repository, ) love_updater := NewSoftwareUpdaterLoveService( software_repository, release_repository, file_repository, + external_link_repository, ) software_updater_service := NewSoftwareUpdaterService(tic80_updater, ebitengine_updater, love_updater) @@ -52,6 +57,7 @@ func NewDomain() Domain { SoftwareRepository: software_repository, ReleaseRepository: release_repository, FileRepository: file_repository, + ExternalLinkRepository: external_link_repository, DownloadService: download_service, SoftwareUpdaterService: software_updater_service, SoftwareService: software_service, diff --git a/apps/catalog/domain/model.external_link.go b/apps/catalog/domain/model.external_link.go new file mode 100644 index 0000000..6590876 --- /dev/null +++ b/apps/catalog/domain/model.external_link.go @@ -0,0 +1,10 @@ +package domain + +import "gorm.io/gorm" + +type ExternalLink struct { + gorm.Model + SoftwareID uint `gorm:"index" json:"softwareId"` + Label string `gorm:"size:128" json:"label"` + URL string `gorm:"size:255" json:"url"` +} diff --git a/apps/catalog/domain/model.migrate.go b/apps/catalog/domain/model.migrate.go index 4dbf09c..d82689a 100644 --- a/apps/catalog/domain/model.migrate.go +++ b/apps/catalog/domain/model.migrate.go @@ -8,5 +8,6 @@ func MigrateGoDatabase(db *gorm.DB) { db.AutoMigrate( Software{}, Release{}, + ExternalLink{}, ) } diff --git a/apps/catalog/domain/model.software.go b/apps/catalog/domain/model.software.go index ff8497f..c0365b4 100644 --- a/apps/catalog/domain/model.software.go +++ b/apps/catalog/domain/model.software.go @@ -4,14 +4,15 @@ import "gorm.io/gorm" type Software struct { gorm.Model - Name string `gorm:"uniqueIndex;size:128" json:"name"` - Title string `gorm:"size:255" json:"title"` - Author string `gorm:"size:255" json:"author"` - Desc string `gorm:"type:text" json:"desc"` - Site string `gorm:"size:255" json:"site"` - License string `gorm:"size:128" json:"license"` - Platform string `gorm:"size:128" json:"platform"` - Status string `gorm:"size:20;default:development" json:"status"` - Highlighted bool `gorm:"default:false" json:"highlighted"` - Releases []Release `json:"-"` + Name string `gorm:"uniqueIndex;size:128" json:"name"` + Title string `gorm:"size:255" json:"title"` + Author string `gorm:"size:255" json:"author"` + Desc string `gorm:"type:text" json:"desc"` + Story string `gorm:"type:text" json:"story"` + License string `gorm:"size:128" json:"license"` + Platform string `gorm:"size:128" json:"platform"` + Status string `gorm:"size:20;default:development" json:"status"` + Highlighted bool `gorm:"default:false" json:"highlighted"` + Releases []Release `json:"-"` + ExternalLinks []ExternalLink `json:"externalLinks"` } diff --git a/apps/catalog/domain/repository.external_link.go b/apps/catalog/domain/repository.external_link.go new file mode 100644 index 0000000..8990425 --- /dev/null +++ b/apps/catalog/domain/repository.external_link.go @@ -0,0 +1,23 @@ +package domain + +import "gorm.io/gorm" + +type ExternalLinkRepositoryInterface interface { + UpsertByLabel(softwareID uint, label, url string) error +} + +type ExternalLinkRepository struct { + db *gorm.DB +} + +func (r *ExternalLinkRepository) UpsertByLabel(softwareID uint, label, url string) error { + var link ExternalLink + err := r.db.Where("software_id = ? AND label = ?", softwareID, label).First(&link).Error + if err == gorm.ErrRecordNotFound { + return r.db.Create(&ExternalLink{SoftwareID: softwareID, Label: label, URL: url}).Error + } + if err != nil { + return err + } + return r.db.Model(&link).Update("url", url).Error +} diff --git a/apps/catalog/domain/repository.software.go b/apps/catalog/domain/repository.software.go index e5e79f6..e37e848 100644 --- a/apps/catalog/domain/repository.software.go +++ b/apps/catalog/domain/repository.software.go @@ -15,7 +15,7 @@ type SoftwareRepository struct { func (r *SoftwareRepository) GetHighlighted() (*Software, error) { var software Software - if err := r.db.Preload("Releases").Where("highlighted = 1").Order("id DESC").First(&software).Error; err != nil { + if err := r.db.Preload("Releases").Preload("ExternalLinks").Where("highlighted = 1").Order("id DESC").First(&software).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, nil } @@ -27,7 +27,7 @@ func (r *SoftwareRepository) GetHighlighted() (*Software, error) { func (r *SoftwareRepository) GetByName(name string) (*Software, error) { var software Software - if err := r.db.Preload("Releases").Where("name = ?", name).First(&software).Error; err != nil { + if err := r.db.Preload("Releases").Preload("ExternalLinks").Where("name = ?", name).First(&software).Error; err != nil { return nil, err } return &software, nil @@ -35,7 +35,7 @@ func (r *SoftwareRepository) GetByName(name string) (*Software, error) { func (r *SoftwareRepository) List() ([]Software, error) { var softwares []Software - if err := r.db.Preload("Releases").Find(&softwares).Error; err != nil { + if err := r.db.Preload("Releases").Preload("ExternalLinks").Find(&softwares).Error; err != nil { return nil, err } return softwares, nil diff --git a/apps/catalog/domain/service.software_updater_ebitengine.go b/apps/catalog/domain/service.software_updater_ebitengine.go index c976f14..e4b7f51 100644 --- a/apps/catalog/domain/service.software_updater_ebitengine.go +++ b/apps/catalog/domain/service.software_updater_ebitengine.go @@ -20,20 +20,23 @@ type SoftwareUpdaterEbitengineServiceInterface interface { } type SoftwareUpdaterEbitengineService struct { - softwareRepository SoftwareRepositoryInterface - releaseRepository ReleaseRepositoryInterface - fileRepository FileRepositoryInterface + softwareRepository SoftwareRepositoryInterface + releaseRepository ReleaseRepositoryInterface + fileRepository FileRepositoryInterface + externalLinkRepository ExternalLinkRepositoryInterface } func NewSoftwareUpdaterEbitengineService( software_repository SoftwareRepositoryInterface, release_repository ReleaseRepositoryInterface, file_repository FileRepositoryInterface, + external_link_repository ExternalLinkRepositoryInterface, ) *SoftwareUpdaterEbitengineService { return &SoftwareUpdaterEbitengineService{ - softwareRepository: software_repository, - releaseRepository: release_repository, - fileRepository: file_repository, + softwareRepository: software_repository, + releaseRepository: release_repository, + fileRepository: file_repository, + externalLinkRepository: external_link_repository, } } @@ -72,6 +75,7 @@ func (s *SoftwareUpdaterEbitengineService) Update(name, version string) error { return err } fmt.Printf("Ebitengine Updater: Extracted metadata: %+v\n", meta_data) + siteURL := meta_data.Site software := s.BuildSoftware(meta_data) err = s.softwareRepository.UpdateOrCreate(software) @@ -80,6 +84,12 @@ func (s *SoftwareUpdaterEbitengineService) Update(name, version string) error { return err } + if siteURL != "" { + if linkErr := s.externalLinkRepository.UpsertByLabel(software.ID, "Source Code", siteURL); linkErr != nil { + fmt.Printf("Ebitengine Updater: Error upserting external link: %s\n", linkErr.Error()) + } + } + release := Release{ SoftwareID: software.ID, Version: version, @@ -101,7 +111,6 @@ func (s *SoftwareUpdaterEbitengineService) BuildSoftware(metadata SoftwareUpdate Title: metadata.Title, Author: metadata.Author, Desc: metadata.Desc, - Site: metadata.Site, License: metadata.License, Platform: "ebitengine", } diff --git a/apps/catalog/domain/service.software_updater_love.go b/apps/catalog/domain/service.software_updater_love.go index 0ef0f4c..2bad0f9 100644 --- a/apps/catalog/domain/service.software_updater_love.go +++ b/apps/catalog/domain/service.software_updater_love.go @@ -20,20 +20,23 @@ type SoftwareUpdaterLoveServiceInterface interface { } type SoftwareUpdaterLoveService struct { - softwareRepository SoftwareRepositoryInterface - releaseRepository ReleaseRepositoryInterface - fileRepository FileRepositoryInterface + softwareRepository SoftwareRepositoryInterface + releaseRepository ReleaseRepositoryInterface + fileRepository FileRepositoryInterface + externalLinkRepository ExternalLinkRepositoryInterface } func NewSoftwareUpdaterLoveService( software_repository SoftwareRepositoryInterface, release_repository ReleaseRepositoryInterface, file_repository FileRepositoryInterface, + external_link_repository ExternalLinkRepositoryInterface, ) *SoftwareUpdaterLoveService { return &SoftwareUpdaterLoveService{ - softwareRepository: software_repository, - releaseRepository: release_repository, - fileRepository: file_repository, + softwareRepository: software_repository, + releaseRepository: release_repository, + fileRepository: file_repository, + externalLinkRepository: external_link_repository, } } @@ -72,6 +75,7 @@ func (s *SoftwareUpdaterLoveService) Update(name, version string) error { return err } fmt.Printf("Love Updater: Extracted metadata: %+v\n", meta_data) + siteURL := meta_data.Site software := s.BuildSoftware(meta_data) err = s.softwareRepository.UpdateOrCreate(software) @@ -80,6 +84,12 @@ func (s *SoftwareUpdaterLoveService) Update(name, version string) error { return err } + if siteURL != "" { + if linkErr := s.externalLinkRepository.UpsertByLabel(software.ID, "Source Code", siteURL); linkErr != nil { + fmt.Printf("Love Updater: Error upserting external link: %s\n", linkErr.Error()) + } + } + release := Release{ SoftwareID: software.ID, Version: version, @@ -101,7 +111,6 @@ func (s *SoftwareUpdaterLoveService) BuildSoftware(metadata SoftwareUpdaterLoveM Title: metadata.Title, Author: metadata.Author, Desc: metadata.Desc, - Site: metadata.Site, License: metadata.License, Platform: "love", } diff --git a/apps/catalog/domain/service.software_updater_tic80.go b/apps/catalog/domain/service.software_updater_tic80.go index 79b3c8c..3e9d114 100644 --- a/apps/catalog/domain/service.software_updater_tic80.go +++ b/apps/catalog/domain/service.software_updater_tic80.go @@ -12,20 +12,23 @@ type SoftwareUpdaterTIC80ServiceInterface interface { } type SoftwareUpdaterTIC80Service struct { - softwareRepository SoftwareRepositoryInterface - releaseRepository ReleaseRepositoryInterface - fileRepository FileRepositoryInterface + softwareRepository SoftwareRepositoryInterface + releaseRepository ReleaseRepositoryInterface + fileRepository FileRepositoryInterface + externalLinkRepository ExternalLinkRepositoryInterface } func NewSoftwareUpdaterTIC80Service( software_repository SoftwareRepositoryInterface, release_repository ReleaseRepositoryInterface, file_repository FileRepositoryInterface, + external_link_repository ExternalLinkRepositoryInterface, ) *SoftwareUpdaterTIC80Service { return &SoftwareUpdaterTIC80Service{ - softwareRepository: software_repository, - releaseRepository: release_repository, - fileRepository: file_repository, + softwareRepository: software_repository, + releaseRepository: release_repository, + fileRepository: file_repository, + externalLinkRepository: external_link_repository, } } @@ -79,6 +82,7 @@ func (s *SoftwareUpdaterTIC80Service) Update(name, version string) error { return err } + siteURL := meta_data["site"] software := s.BuildSoftware(meta_data) err = s.softwareRepository.UpdateOrCreate(software) if err != nil { @@ -86,6 +90,12 @@ func (s *SoftwareUpdaterTIC80Service) Update(name, version string) error { return err } + if siteURL != "" { + if linkErr := s.externalLinkRepository.UpsertByLabel(software.ID, "Source Code", siteURL); linkErr != nil { + fmt.Printf("TIC80 Updater: Error upserting external link: %s\n", linkErr.Error()) + } + } + fmt.Printf("TIC80 Updater: Creating release for software ID: %d, version: %s\n", software.ID, version) release := Release{ SoftwareID: software.ID, @@ -108,7 +118,6 @@ func (s *SoftwareUpdaterTIC80Service) BuildSoftware(metadata map[string]string) Title: metadata["title"], Author: metadata["author"], Desc: metadata["desc"], - Site: metadata["site"], License: metadata["license"], Platform: "tic80", } diff --git a/apps/frontend/src/pages/catalog/[name].astro b/apps/frontend/src/pages/catalog/[name].astro index a4bd3b0..0f08183 100644 --- a/apps/frontend/src/pages/catalog/[name].astro +++ b/apps/frontend/src/pages/catalog/[name].astro @@ -78,23 +78,31 @@ const latestStable = stableReleases[0];
{software.license}
)} - {software.site && ( + {software.externalLinks && software.externalLinks.map((link: any) => (
-
Source Code
+
{link.label}
- - Visit Repository ↗ + + {link.url} ↗
- )} + ))}
- + + + {software.story && ( +
+

About

+

{software.story}

+
+ )} + {latestStable && (