ExternalLink

This commit is contained in:
2026-05-04 14:45:30 +02:00
parent c5fac10048
commit 189bce21e1
11 changed files with 124 additions and 40 deletions
+6
View File
@@ -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,
@@ -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"`
}
+1
View File
@@ -8,5 +8,6 @@ func MigrateGoDatabase(db *gorm.DB) {
db.AutoMigrate(
Software{},
Release{},
ExternalLink{},
)
}
+11 -10
View File
@@ -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"`
}
@@ -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
}
+3 -3
View File
@@ -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
@@ -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",
}
@@ -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",
}
@@ -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",
}
+14 -6
View File
@@ -78,23 +78,31 @@ const latestStable = stableReleases[0];
<dd class="text-gray-900 font-medium">{software.license}</dd>
</div>
)}
{software.site && (
{software.externalLinks && software.externalLinks.map((link: any) => (
<div>
<dt class="text-xs font-bold text-gray-400 uppercase tracking-widest mb-1">Source Code</dt>
<dt class="text-xs font-bold text-gray-400 uppercase tracking-widest mb-1">{link.label}</dt>
<dd>
<a href={software.site} target="_blank" class="text-purple-600 hover:text-purple-800 font-bold break-all">
Visit Repository
<a href={link.url} target="_blank" class="text-purple-600 hover:text-purple-800 font-bold break-all">
{link.url}
</a>
</dd>
</div>
)}
))}
</dl>
</div>
</div>
<!-- Main Content Area -->
<div class="lg:col-span-2 space-y-8">
<!-- Story -->
{software.story && (
<div class="bg-white rounded-3xl shadow-xl p-8 border border-gray-100">
<h2 class="text-xl font-bold text-gray-900 mb-4">About</h2>
<p class="text-gray-700 leading-relaxed whitespace-pre-line">{software.story}</p>
</div>
)}
<!-- Latest Release (Prominent Section) -->
{latestStable && (
<div class="bg-gradient-to-br from-indigo-600 to-purple-700 rounded-3xl shadow-xl p-8 text-white">