From 3822709746cf9ef8be5644ead72cb0d9c8e6d1ef Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Sun, 15 Mar 2026 01:21:51 +0100 Subject: [PATCH] highlighted --- apps/catalog/domain/model.software.go | 5 +- apps/catalog/domain/repository.software.go | 13 ++ apps/catalog/domain/service.software.go | 13 ++ apps/catalog/http/controller.api.software.go | 1 + .../controller.api.software_highlighted.go | 45 ++++++ apps/catalog/http/http.go | 1 + apps/catalog/http/router.go | 12 +- apps/frontend/src/pages/index.astro | 139 +++++++++++++++++- 8 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 apps/catalog/http/controller.api.software_highlighted.go diff --git a/apps/catalog/domain/model.software.go b/apps/catalog/domain/model.software.go index e69e65e..ff8497f 100644 --- a/apps/catalog/domain/model.software.go +++ b/apps/catalog/domain/model.software.go @@ -11,6 +11,7 @@ type Software struct { 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"` - Releases []Release `json:"-"` + Status string `gorm:"size:20;default:development" json:"status"` + Highlighted bool `gorm:"default:false" json:"highlighted"` + Releases []Release `json:"-"` } diff --git a/apps/catalog/domain/repository.software.go b/apps/catalog/domain/repository.software.go index 2446b14..e5e79f6 100644 --- a/apps/catalog/domain/repository.software.go +++ b/apps/catalog/domain/repository.software.go @@ -6,12 +6,25 @@ type SoftwareRepositoryInterface interface { List() ([]Software, error) GetByName(name string) (*Software, error) UpdateOrCreate(software *Software) error + GetHighlighted() (*Software, error) } type SoftwareRepository struct { db *gorm.DB } +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 == gorm.ErrRecordNotFound { + return nil, nil + } + return nil, err + } + return &software, nil +} + + 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 { diff --git a/apps/catalog/domain/service.software.go b/apps/catalog/domain/service.software.go index bb44985..810c652 100644 --- a/apps/catalog/domain/service.software.go +++ b/apps/catalog/domain/service.software.go @@ -27,6 +27,7 @@ type SoftwareServiceInterface interface { GetByName(name string) (*Software, error) GetLatestRelease(software_id string) (*Release, error) GetForShowByName(name string) (*SoftwareShowData, error) + GetHighlighted() (*SoftwareShowData, error) } type SoftwareService struct { @@ -34,6 +35,18 @@ type SoftwareService struct { release_repository ReleaseRepositoryInterface } +func (s *SoftwareService) GetHighlighted() (*SoftwareShowData, error) { + software, err := s.softeare_repository.GetHighlighted() + if err != nil { + return nil, err + } + if software == nil { + return nil, nil + } + + return s.GetForShowByName(software.Name) +} + func NewSoftwareService( softeare_repository SoftwareRepositoryInterface, release_repository ReleaseRepositoryInterface, diff --git a/apps/catalog/http/controller.api.software.go b/apps/catalog/http/controller.api.software.go index 13259e3..943a0e2 100644 --- a/apps/catalog/http/controller.api.software.go +++ b/apps/catalog/http/controller.api.software.go @@ -24,6 +24,7 @@ func replaceReleasePaths(release *domain.Release) { } func (c *APISoftwareController) Index(w http.ResponseWriter, r *http.Request) { + softwares, _ := c.softwareService.DetailedList() for idx := range softwares.Softwares { diff --git a/apps/catalog/http/controller.api.software_highlighted.go b/apps/catalog/http/controller.api.software_highlighted.go new file mode 100644 index 0000000..bc6184b --- /dev/null +++ b/apps/catalog/http/controller.api.software_highlighted.go @@ -0,0 +1,45 @@ +package http + +import ( + "encoding/json" + "net/http" + + "teletype_softwares/domain" +) + +type APISoftwareHighlightedController struct { + softwareService domain.SoftwareServiceInterface +} + +func NewAPISoftwareHighlightedController(software_service domain.SoftwareServiceInterface) *APISoftwareHighlightedController { + return &APISoftwareHighlightedController{softwareService: software_service} +} + +func (c *APISoftwareHighlightedController) Index(w http.ResponseWriter, r *http.Request) { + softwareShowData, err := c.softwareService.GetHighlighted() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if softwareShowData == nil { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(map[string]string{"error": "no highlighted software found"}) + return + } + + for i := range softwareShowData.Releases { + replaceReleasePaths(&softwareShowData.Releases[i]) + } + + if softwareShowData.LatestRelease != nil { + replaceReleasePaths(softwareShowData.LatestRelease) + } + if softwareShowData.WebPlayableRelease != nil { + replaceReleasePaths(softwareShowData.WebPlayableRelease) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(softwareShowData) +} diff --git a/apps/catalog/http/http.go b/apps/catalog/http/http.go index 82cbba6..c643f50 100755 --- a/apps/catalog/http/http.go +++ b/apps/catalog/http/http.go @@ -8,6 +8,7 @@ import ( func StartHttpServer(domain_instance domain.Domain) { router := NewRouter( NewAPISoftwareController(domain_instance.SoftwareService), + NewAPISoftwareHighlightedController(domain_instance.SoftwareService), NewSoftwareUpdaterController(domain_instance.SoftwareUpdaterService, domain_instance.SoftwareService), ).Init() diff --git a/apps/catalog/http/router.go b/apps/catalog/http/router.go index 2a1837c..fbf4e78 100755 --- a/apps/catalog/http/router.go +++ b/apps/catalog/http/router.go @@ -7,17 +7,20 @@ import ( ) type Router struct { - apiSoftwareController *APISoftwareController - softwareUpdaterController *SoftwareUpdaterController + apiSoftwareController *APISoftwareController + apiSoftwareHighlightedController *APISoftwareHighlightedController + softwareUpdaterController *SoftwareUpdaterController } func NewRouter( api_software_controller *APISoftwareController, + api_software_highlighted_controller *APISoftwareHighlightedController, software_updater_controller *SoftwareUpdaterController, ) *Router { return &Router{ - apiSoftwareController: api_software_controller, - softwareUpdaterController: software_updater_controller, + apiSoftwareController: api_software_controller, + apiSoftwareHighlightedController: api_software_highlighted_controller, + softwareUpdaterController: software_updater_controller, } } @@ -26,6 +29,7 @@ func (r *Router) Init() *chi.Mux { router.Use(CORSMiddleware) router.Get("/api/software", r.apiSoftwareController.Index) + router.Get("/api/software/highlighted", r.apiSoftwareHighlightedController.Index) router.Get("/update", r.softwareUpdaterController.Update) fs_file := http.FileServer(http.Dir("/softwares")) diff --git a/apps/frontend/src/pages/index.astro b/apps/frontend/src/pages/index.astro index ccce2fb..f2c9871 100644 --- a/apps/frontend/src/pages/index.astro +++ b/apps/frontend/src/pages/index.astro @@ -63,6 +63,20 @@ try { } catch (e) { console.error('Failed to load events:', e); } + +// Fetch highlighted software +const BASE = "https://games.teletype.hu"; +const API_HIGHLIGHTED = BASE + "/api/software/highlighted"; +let highlightedSoftware = null; + +try { + const res = await fetch(API_HIGHLIGHTED); + if (res.ok) { + highlightedSoftware = await res.json(); + } +} catch (e) { + console.error('Failed to fetch highlighted software:', e); +} --- @@ -115,6 +129,40 @@ try { )} + + {highlightedSoftware && ( +
+ +
+ )} +
@@ -171,7 +219,96 @@ try { .home-header-decor { @apply absolute inset-0 opacity-30; } - + + /* Featured Card */ + .featured-card { + @apply relative overflow-hidden bg-gradient-to-br from-indigo-900 to-slate-900 rounded-3xl shadow-2xl flex flex-col md:flex-row min-h-[400px]; + } + .featured-content { + @apply p-8 md:p-12 flex flex-col justify-center flex-1 z-10; + } + .featured-badge { + @apply px-3 py-1 bg-indigo-500/20 text-indigo-300 border border-indigo-400/30 rounded-full text-xs font-bold uppercase tracking-wider; + } + .featured-title { + @apply text-4xl md:text-5xl font-black text-white mb-6; + } + .featured-desc { + @apply text-indigo-100/80 text-lg leading-relaxed max-w-xl; + } + .featured-btn-play { + @apply px-8 py-4 bg-white text-indigo-900 font-bold rounded-xl transition-all hover:scale-105 hover:shadow-xl hover:shadow-indigo-500/20 active:scale-95; + } + .featured-btn-secondary { + @apply px-8 py-4 bg-indigo-500/10 text-white border border-indigo-400/30 font-bold rounded-xl transition-all hover:bg-indigo-500/20; + } + .featured-visual { + @apply flex-1 relative min-h-[300px] md:min-h-full overflow-hidden; + } + .featured-placeholder { + @apply absolute inset-0 flex items-center justify-center bg-indigo-500/5; + } + .featured-glow { + @apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-64 h-64 bg-indigo-500/30 rounded-full blur-[100px] pointer-events-none; + } + .text-8-xl { + font-size: 8rem; + } + + /* Status Badges */ + .status-badge { + @apply px-2 py-0.5 rounded text-[10px] font-bold uppercase; + } + .status-released { @apply bg-green-500/20 text-green-300 border border-green-500/30; } + .status-demo { @apply bg-blue-500/20 text-blue-300 border border-blue-500/30; } + .status-development { @apply bg-yellow-500/20 text-yellow-300 border border-yellow-500/30; } + .status-archived { @apply bg-gray-500/20 text-gray-300 border border-gray-500/30; } + + /* Countdown Card */ + + .featured-card { + @apply relative overflow-hidden bg-gradient-to-br from-indigo-900 to-slate-900 rounded-3xl shadow-2xl flex flex-col md:flex-row min-h-[400px]; + } + .featured-content { + @apply p-8 md:p-12 flex flex-col justify-center flex-1 z-10; + } + .featured-badge { + @apply px-3 py-1 bg-indigo-500/20 text-indigo-300 border border-indigo-400/30 rounded-full text-xs font-bold uppercase tracking-wider; + } + .featured-title { + @apply text-4xl md:text-5xl font-black text-white mb-6; + } + .featured-desc { + @apply text-indigo-100/80 text-lg leading-relaxed max-w-xl; + } + .featured-btn-play { + @apply px-8 py-4 bg-white text-indigo-900 font-bold rounded-xl transition-all hover:scale-105 hover:shadow-xl hover:shadow-indigo-500/20 active:scale-95; + } + .featured-btn-secondary { + @apply px-8 py-4 bg-indigo-500/10 text-white border border-indigo-400/30 font-bold rounded-xl transition-all hover:bg-indigo-500/20; + } + .featured-visual { + @apply flex-1 relative min-h-[300px] md:min-h-full overflow-hidden; + } + .featured-placeholder { + @apply absolute inset-0 flex items-center justify-center bg-indigo-500/5; + } + .featured-glow { + @apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-64 h-64 bg-indigo-500/30 rounded-full blur-[100px] pointer-events-none; + } + .text-8-xl { + font-size: 8rem; + } + + /* Status Badges */ + .status-badge { + @apply px-2 py-0.5 rounded text-[10px] font-bold uppercase; + } + .status-released { @apply bg-green-500/20 text-green-300 border border-green-500/30; } + .status-demo { @apply bg-blue-500/20 text-blue-300 border border-blue-500/30; } + .status-development { @apply bg-yellow-500/20 text-yellow-300 border border-yellow-500/30; } + .status-archived { @apply bg-gray-500/20 text-gray-300 border border-gray-500/30; } + /* Countdown Card */ .countdown-card { @apply bg-white rounded-2xl shadow-xl overflow-hidden border border-gray-100 p-8 md:p-12 text-center transition-all duration-300;