purge MPA version

This commit is contained in:
2026-03-02 23:06:38 +01:00
parent aa6c8724ef
commit 31135e0762
14 changed files with 0 additions and 694 deletions
View File
View File
-70
View File
@@ -1,70 +0,0 @@
package http
import (
"fmt"
"net/http"
"os"
"teletype_softwares/domain"
"github.com/go-chi/chi/v5"
)
type DocsController struct {
softwareService domain.SoftwareServiceInterface
fileService domain.FileServiceInterface
}
func NewDocsController(software_service domain.SoftwareServiceInterface, file_service domain.FileServiceInterface) *DocsController {
return &DocsController{
softwareService: software_service,
fileService: file_service,
}
}
func (c *DocsController) ServeDocs(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
version := chi.URLParam(r, "version")
software, err := c.softwareService.GetByName(name)
if err != nil {
if err == domain.ErrSoftwareNotFound {
http.Error(w, "Software not found", http.StatusNotFound)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var targetRelease *domain.Release
for _, release := range software.Releases {
if release.Version == version && release.DocsFolderPath != "" {
targetRelease = &release
break
}
}
if targetRelease == nil {
http.Error(w, "No documentation found for this release", http.StatusNotFound)
return
}
docs_base_dir := targetRelease.DocsFolderPath
if _, err := os.Stat(docs_base_dir); os.IsNotExist(err) {
http.Error(w, fmt.Sprintf("Documentation for '%s' version '%s' not found.", name, version), http.StatusNotFound)
return
} else if err != nil {
http.Error(w, fmt.Sprintf("Error accessing documentation for '%s' version '%s': %v", name, version, err), http.StatusInternalServerError)
return
}
prefix := fmt.Sprintf("/docs/%s/%s", name, version)
if r.URL.Path == prefix {
http.Redirect(w, r, prefix+"/", http.StatusMovedPermanently)
return
}
fs := http.StripPrefix(prefix, http.FileServer(http.Dir(docs_base_dir)))
fs.ServeHTTP(w, r)
}
-114
View File
@@ -1,114 +0,0 @@
package http
import (
"net/http"
"os"
"path/filepath"
"strings"
"teletype_softwares/domain"
"github.com/go-chi/chi/v5"
)
type DownloadController struct {
downloadService domain.DownloadServiceInterface
softwareService domain.SoftwareServiceInterface
}
func NewDownloadController(
downloadService domain.DownloadServiceInterface,
softwareService domain.SoftwareServiceInterface,
) *DownloadController {
return &DownloadController{
downloadService: downloadService,
softwareService: softwareService,
}
}
func (c *DownloadController) serve(w http.ResponseWriter, r *http.Request, release *domain.Release, is_source bool) {
var file_path string
if is_source {
file_path = release.SourcePath
} else {
file_path = release.CartridgePath
}
abs_file_path, err := filepath.Abs(file_path)
if err != nil {
http.Error(w, "Invalid file path", http.StatusInternalServerError)
return
}
abs_contents_dir, err := filepath.Abs(os.Getenv("FILE_CONTAINER_PATH"))
if err != nil {
http.Error(w, "Invalid contents directory path", http.StatusInternalServerError)
return
}
if !strings.HasPrefix(abs_file_path, abs_contents_dir) {
http.Error(w, "Access denied", http.StatusForbidden)
return
}
w.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(file_path))
http.ServeFile(w, r, file_path)
}
func (c *DownloadController) handleError(w http.ResponseWriter, err error) {
if err == domain.ErrSoftwareNotFound || err == domain.ErrNoReleasesFound {
http.Error(w, "Software not found", http.StatusNotFound)
return
}
if err == domain.ErrReleaseNotFound {
http.Error(w, "Software or release not found", http.StatusNotFound)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
}
func (c *DownloadController) GetLatestSource(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
release, err := c.downloadService.GetLatestRelease(name)
if err != nil {
c.handleError(w, err)
return
}
c.serve(w, r, release, true)
}
func (c *DownloadController) GetLatestCartridge(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
release, err := c.downloadService.GetLatestRelease(name)
if err != nil {
c.handleError(w, err)
return
}
c.serve(w, r, release, false)
}
func (c *DownloadController) GetSource(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
version := chi.URLParam(r, "version")
release, err := c.downloadService.GetSpecificRelease(name, version)
if err != nil {
c.handleError(w, err)
return
}
c.serve(w, r, release, true)
}
func (c *DownloadController) GetCartridge(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
version := chi.URLParam(r, "version")
release, err := c.downloadService.GetSpecificRelease(name, version)
if err != nil {
c.handleError(w, err)
return
}
c.serve(w, r, release, false)
}
-95
View File
@@ -1,95 +0,0 @@
package http
import (
"fmt"
"net/http"
"os"
"teletype_softwares/domain"
"teletype_softwares/lib/template_utils"
"github.com/go-chi/chi/v5"
)
type PlayController struct {
softwareService domain.SoftwareServiceInterface
fileService domain.FileServiceInterface
}
func NewPlayController(software_service domain.SoftwareServiceInterface, file_service domain.FileServiceInterface) *PlayController {
return &PlayController{
softwareService: software_service,
fileService: file_service,
}
}
func (c *PlayController) Play(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
version := chi.URLParam(r, "version")
software, err := c.softwareService.GetByName(name)
if err != nil {
if err == domain.ErrSoftwareNotFound {
http.Error(w, "Software not found", http.StatusNotFound)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var web_playable_release *domain.Release
for _, release := range software.Releases {
if release.Version == version && release.HTMLFolderPath != "" {
web_playable_release = &release
break
}
}
if web_playable_release == nil {
http.Error(w, "No web-playable version found for this software", http.StatusNotFound)
return
}
softwares, err := c.softwareService.List()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl, err := template_utils.GetTemplate("play_controller_play", "http/views/shared/layout.html", "http/views/play/play.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl.Execute(w, map[string]interface{}{
"Software": software,
"WebPlayableRelease": web_playable_release,
"Softwares": softwares,
})
}
func (c *PlayController) ServeContent(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
version := chi.URLParam(r, "version")
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
w.Header().Set("Cross-Origin-Embedder-Policy", "require-corp")
w.Header().Set("Cross-Origin-Resource-Policy", "same-origin")
html_base_dir := c.fileService.GetPath(name + "-" + version)
if _, err := os.Stat(html_base_dir); os.IsNotExist(err) {
http.Error(w, fmt.Sprintf("Content for '%s' not found.", name), http.StatusNotFound)
return
} else if err != nil {
http.Error(w, fmt.Sprintf("Error accessing content for '%s': %v", name, err), http.StatusInternalServerError)
return
}
fs := http.StripPrefix(
fmt.Sprintf("/play/%s/%s/content", name, version),
http.FileServer(http.Dir(html_base_dir)),
)
fs.ServeHTTP(w, r)
}
-38
View File
@@ -1,38 +0,0 @@
package http
import (
"net/http"
"teletype_softwares/domain"
"teletype_softwares/lib/template_utils"
)
type RootController struct {
softwareService domain.SoftwareServiceInterface
}
func NewRootController(softwareService domain.SoftwareServiceInterface) *RootController {
return &RootController{
softwareService: softwareService,
}
}
func (c *RootController) Index(w http.ResponseWriter, r *http.Request) {
softwares, err := c.softwareService.List()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := map[string]interface{}{
"Softwares": softwares,
}
tmpl, err := template_utils.GetTemplate("root_index", "http/views/shared/layout.html", "http/views/root/index.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl.Execute(w, data)
}
-70
View File
@@ -1,70 +0,0 @@
package http
import (
"net/http"
"teletype_softwares/domain"
"teletype_softwares/lib/template_utils"
"github.com/go-chi/chi/v5"
)
type SoftwareController struct {
softwareService domain.SoftwareServiceInterface
}
func NewSoftwareController(software_service domain.SoftwareServiceInterface) *SoftwareController {
return &SoftwareController{softwareService: software_service}
}
func (c *SoftwareController) Index(w http.ResponseWriter, r *http.Request) {
softwares, err := c.softwareService.List()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := map[string]interface{}{
"Softwares": softwares,
}
tmpl, err := template_utils.GetTemplate("software_index", "http/views/shared/layout.html", "http/views/software/index.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl.Execute(w, data)
}
func (c *SoftwareController) Show(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
showData, err := c.softwareService.GetForShowByName(name)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
softwares, err := c.softwareService.List()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := map[string]interface{}{
"Software": showData.Software,
"Releases": showData.Releases,
"LatestRelease": showData.LatestRelease,
"WebPlayableRelease": showData.WebPlayableRelease,
"Softwares": softwares,
}
tmpl, err := template_utils.GetTemplate("software_show", "http/views/shared/layout.html", "http/views/software/show.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl.Execute(w, data)
}
-5
View File
@@ -8,12 +8,7 @@ import (
func StartHttpServer(domain_instance domain.Domain) {
router := NewRouter(
NewAPISoftwareController(domain_instance.SoftwareService),
NewSoftwareController(domain_instance.SoftwareService),
NewSoftwareUpdaterController(domain_instance.SoftwareUpdaterService, domain_instance.SoftwareService),
NewDownloadController(domain_instance.DownloadService, domain_instance.SoftwareService),
NewPlayController(domain_instance.SoftwareService, domain_instance.FileService),
NewRootController(domain_instance.SoftwareService),
NewDocsController(domain_instance.SoftwareService, domain_instance.FileService),
).Init()
http_utils.StartGenericHTTPServer(http_utils.StartGenericHTTPServerContext{
-32
View File
@@ -8,31 +8,16 @@ import (
type Router struct {
apiSoftwareController *APISoftwareController
softwareController *SoftwareController
softwareUpdaterController *SoftwareUpdaterController
downloadController *DownloadController
playController *PlayController
rootController *RootController
docsController *DocsController
}
func NewRouter(
api_software_controller *APISoftwareController,
software_controller *SoftwareController,
software_updater_controller *SoftwareUpdaterController,
download_controller *DownloadController,
play_controller *PlayController,
root_controller *RootController,
docs_controller *DocsController,
) *Router {
return &Router{
apiSoftwareController: api_software_controller,
softwareController: software_controller,
softwareUpdaterController: software_updater_controller,
downloadController: download_controller,
playController: play_controller,
rootController: root_controller,
docsController: docs_controller,
}
}
@@ -40,25 +25,8 @@ func (r *Router) Init() *chi.Mux {
router := chi.NewRouter()
router.Use(CORSMiddleware)
router.Get("/", r.rootController.Index)
router.Get("/api/software", r.apiSoftwareController.Index)
router.Get("/software", r.softwareController.Index)
router.Get("/software/{name}", r.softwareController.Show)
router.Get("/update", r.softwareUpdaterController.Update)
router.Get("/download/{name}/source", r.downloadController.GetLatestSource)
router.Get("/download/{name}/cartridge", r.downloadController.GetLatestCartridge)
router.Get("/download/{name}/{version}/source", r.downloadController.GetSource)
router.Get("/download/{name}/{version}/cartridge", r.downloadController.GetCartridge)
router.Get("/play/{name}/{version}", r.playController.Play)
router.Get("/play/{name}/{version}/content*", r.playController.ServeContent)
router.Get("/docs/{name}/{version}", r.docsController.ServeDocs)
router.Get("/docs/{name}/{version}/*", r.docsController.ServeDocs)
fs_assets := http.FileServer(http.Dir("assets"))
router.Handle("/assets/*", http.StripPrefix("/assets/", fs_assets))
fs_file := http.FileServer(http.Dir("/softwares"))
router.Handle("/file/*", http.StripPrefix("/file/", fs_file))
-77
View File
@@ -1,77 +0,0 @@
{{define "content"}}
<h1 class="my-4">Play {{.Software.Title}}</h1>
<div class="ratio ratio-4x3" style="max-width: 960px; margin: 0 auto;">
<iframe id="gameFrame" src="/play/{{.Software.Name}}/{{.WebPlayableRelease.Version}}/content/index.html" frameborder="0" allowfullscreen></iframe>
</div>
<div style="max-width: 960px; margin: 10px auto; text-align: center;">
<div class="btn-group" role="group" aria-label="Game controls">
<a href="/releases/{{.Software.Name}}" class="btn btn-primary">Back to Releases</a>
{{if .WebPlayableRelease.DocsFolderPath}}
<a href="/docs/{{.Software.Name}}/{{.WebPlayableRelease.Version}}" class="btn btn-info">Docs</a>
{{end}}
<button id="fullscreenButton" class="btn btn-secondary">Fullscreen</button>
</div>
</div>
<div style="max-width: 960px; margin: 10px auto; text-align: start;">
<h2 class="my-4">Version</h2>
<p>{{.WebPlayableRelease.Version}}</p>
<h2 class="my-4">Other Versions</h2>
<div class="list-group">
{{range .Software.Releases}}
{{if and .WebPlayable (ne .Version $.WebPlayableRelease.Version)}}
<a href="/play/{{$.Software.Name}}/{{.Version}}" class="list-group-item list-group-item-action">
{{.Version}}
</a>
{{end}}
{{end}}
</div>
</div>
<script>
document.getElementById('fullscreenButton').addEventListener('click', function() {
var iframe = document.getElementById('gameFrame');
if (iframe.requestFullscreen) {
iframe.requestFullscreen();
} else if (iframe.mozRequestFullScreen) { /* Firefox */
iframe.mozRequestFullScreen();
} else if (iframe.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
iframe.webkitRequestFullscreen();
} else if (iframe.msRequestFullscreen) { /* IE/Edge */
iframe.msRequestFullscreen();
}
});
document.addEventListener('fullscreenchange', function() {
if (document.fullscreenElement) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
});
document.addEventListener('mozfullscreenchange', function() {
if (document.mozFullScreenElement) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
});
document.addEventListener('webkitfullscreenchange', function() {
if (document.webkitFullscreenElement) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
});
document.addEventListener('msfullscreenchange', function() {
if (document.msFullscreenElement) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
});
</script>
{{end}}
-8
View File
@@ -1,8 +0,0 @@
{{define "content"}}
<h1 class="my-4">Welcome</h1>
<p>
Welcome to Teletype Games! We are an independent, community-driven game development collective. Our mission is to create small, experimental, and full-fledged games in short development cycles while keeping everything open, transparent, and collaborative.
</p>
{{end}}
@@ -1,70 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Teletype Games</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link href="/assets/css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<header class="navbar navbar-expand-lg header-border">
<div class="container-fluid">
<a class="navbar-brand" href="/" >Teletype Games</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://www.youtube.com/@teletypegames" target="_blank">Youtube</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://git.teletype.hu/explore/repos" target="_blank">Repositories</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://wiki.teletype.hu/" target="_blank">Wiki</a>
</li>
</ul>
</div>
</div>
</header>
<div class="container mt-4 mb-4">
<div class="row">
<div class="col-md-3">
<div class="content-border">
<ul class="list-group">
<li class="list-group-item disabled" aria-disabled="true">Softwares</li>
{{range .Softwares}}
<li class="list-group-item">
<a href="/software/{{.Software.Name}}">{{.Software.Name}}</a>
</li>
{{end}}
</ul>
</div>
</div>
<div class="col-md-9">
<div class="content-border p-3">
{{block "content" .}}{{end}}
</div>
</div>
</div>
</div>
<footer class="footer-border text-center py-3">
<p>
<a class="nav-link" href="http://teletype.hu" target="_blank">
Teletype Games 2025-2026
</a>
</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
</body>
</html>
@@ -1,15 +0,0 @@
{{define "content"}}
<h1 class="my-4">Softwares</h1>
<div class="row">
<div class="col-12 mb-4">
<ul>
{{range .Softwares}}
<li>
<a href="/software/{{.Software.Name}}">{{.Software.Title}}</a>
</li>
{{end}}
</ul>
</div>
</div>
{{end}}
-100
View File
@@ -1,100 +0,0 @@
{{define "content"}}
<h1 class="my-4">{{.Software.Title}}</h1>
<div class="row">
{{if .WebPlayableRelease}}
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header">
<h3>Play</h3>
</div>
<div class="card-body">
<div class="ratio ratio-4x3" style="max-width: 960px; margin: 0 auto;">
<iframe id="gameFrame" src="/play/{{.Software.Name}}/{{.WebPlayableRelease.Version}}/content/index.html"
frameborder="0" allowfullscreen></iframe>
</div>
</div>
<div class="card-footer">
<div class="btn-group d-flex justify-content-center" role="group" aria-label="Game controls">
<button id="fullscreenButton" class="btn btn-secondary btn-sm">Fullscreen</button>
</div>
</div>
</div>
</div>
{{end}}
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header">
<h3>Overview</h3>
</div>
<div class="card-body">
<table class="table table-bordered">
<tbody>
<tr>
<th scope="row" style="width: 30%;">Title</th>
<td>{{.Software.Title}}</td>
</tr>
<tr>
<th scope="row">Author</th>
<td>{{.Software.Author}}</td>
</tr>
<tr>
<th scope="row">Description</th>
<td>{{.Software.Desc}}</td>
</tr>
<tr>
<th scope="row">Version</th>
<td>{{.LatestRelease.Version}}</td>
</tr>
<tr>
<th scope="row">License</th>
<td>{{.Software.License}}</td>
</tr>
<tr>
<th scope="row">Website</th>
<td><a href="{{.Software.Site}}" target="_blank">{{.Software.Site}}</a></td>
</tr>
</tbody>
</table>
</div>
<div class="card-footer">
<div class="btn-group d-flex justify-content-end" role="group" aria-label="Operations">
<a href="/download/{{.Software.Name}}/source" class="btn btn-primary btn-sm">Source</a>
<a href="/download/{{.Software.Name}}/cartridge" class="btn btn-primary btn-sm">Cartridge</a>
{{if .WebPlayableRelease.DocsFolderPath}}
<a href="/docs/{{.Software.Name}}/{{.WebPlayableRelease.Version}}" class="btn btn-primary btn-sm">Docs</a>
{{end}}
</div>
</div>
</div>
</div>
</div>
<h3>Releases</h3>
<div class="row">
{{range .Releases}}
<div class="col-12 mb-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title">Version: {{.Version}}</h5>
<p class="card-subtitle mb-2 text-muted">Released At: {{.CreatedAt.Format "2006-01-02 15:04:05"}}</p>
</div>
<div class="card-footer">
<div class="btn-group d-flex justify-content-center" role="group" aria-label="Operations">
{{if .HTMLFolderPath}}
<a href="/play/{{$.Software.Name}}/{{.Version}}" class="btn btn-primary btn-sm">Play</a>
{{end}}
<a href="/download/{{$.Software.Name}}/{{.Version}}/source"
class="btn btn-primary btn-sm">Source</a>
<a href="/download/{{$.Software.Name}}/{{.Version}}/cartridge"
class="btn btn-primary btn-sm">Cartridge</a>
{{if .DocsFolderPath}}
<a href="/docs/{{$.Software.Name}}/{{.Version}}" class="btn btn-primary btn-sm">Docs</a>
{{end}}
</div>
</div>
</div>
</div>
{{end}}
</div>
{{end}}