highlighted

This commit is contained in:
2026-03-15 01:31:39 +01:00
parent c8de1a3f74
commit 3822709746
8 changed files with 222 additions and 7 deletions
+3 -2
View File
@@ -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:"-"`
}
@@ -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 {
+13
View File
@@ -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,
@@ -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 {
@@ -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)
}
+1
View File
@@ -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()
+8 -4
View File
@@ -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"))
+138 -1
View File
@@ -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);
}
---
<Layout>
@@ -115,6 +129,40 @@ try {
</section>
)}
<!-- Featured Software Section -->
{highlightedSoftware && (
<section class="mb-16">
<div class="featured-card group">
<div class="featured-content">
<div class="flex items-center gap-2 mb-4">
<span class="featured-badge">Featured Game</span>
<span class="status-badge status-released">{highlightedSoftware.software.status}</span>
</div>
<h2 class="featured-title">{highlightedSoftware.software.title}</h2>
<p class="featured-desc">{highlightedSoftware.software.desc}</p>
<div class="flex flex-wrap gap-4 mt-8">
{highlightedSoftware.webPlayableRelease && (
<a href={BASE + highlightedSoftware.webPlayableRelease.htmlFolderPath} target="_blank" class="featured-btn-play">
▶ Play in Browser
</a>
)}
<a href="/catalog" class="featured-btn-secondary">
View in Catalog
</a>
</div>
</div>
<div class="featured-visual">
<div class="featured-placeholder">
<span class="text-8-xl opacity-20 group-hover:scale-110 transition-transform duration-500">🎮</span>
<div class="featured-glow"></div>
</div>
</div>
</div>
</section>
)}
<!-- Navigation Grid -->
<section class="card-grid">
<!-- Catalog Section -->
@@ -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;