From e7c0d09456c5b83d7db93166599ee32ec6220cd2 Mon Sep 17 00:00:00 2001
From: serr <sc7227484@gmail.com>
Date: Sat, 5 Apr 2025 19:44:26 +0300
Subject: [PATCH] mvc

---
 main.go                                       | 152 +++---------------
 mvc/controllers/main_page.go                  |  33 ++++
 mvc/controllers/static.go                     |  15 ++
 mvc/models/app.go                             |  65 ++++++++
 {cache => mvc/models}/cache.go                |   4 +-
 {config => mvc/models}/config.go              |   4 +-
 mvc/models/main_page.go                       |  26 +++
 .../main.gohtml => mvc/views/main_page.gohtml |   0
 8 files changed, 161 insertions(+), 138 deletions(-)
 create mode 100644 mvc/controllers/main_page.go
 create mode 100644 mvc/controllers/static.go
 create mode 100644 mvc/models/app.go
 rename {cache => mvc/models}/cache.go (89%)
 rename {config => mvc/models}/config.go (90%)
 create mode 100644 mvc/models/main_page.go
 rename assets/templates/main.gohtml => mvc/views/main_page.gohtml (100%)

diff --git a/main.go b/main.go
index 372d956..817c6c4 100644
--- a/main.go
+++ b/main.go
@@ -1,17 +1,11 @@
 package main
 
 import (
-	"bytes"
 	"log"
-	"main/cache"
-	"main/config"
+	"main/mvc/controllers"
+	"main/mvc/models"
 	"main/tools"
 	"net/http"
-	"os"
-	"path/filepath"
-	"strings"
-	"text/template"
-	"time"
 )
 
 /*
@@ -20,163 +14,53 @@ go build main.go
 sudo systemctl start server.service
 */
 
-// App представляет основное состояние приложения
-type app struct {
-	templates *template.Template // Шаблоны страниц
-	config    *config.Config     // Конфиг
-	startTime int64              // Время запуска
-	cache     *cache.Cache       // Кэш (отрендеренные странички)
-}
-
 func main() {
-	var app *app
+	var app *models.App
 	var err error
 
 	// Инициализация приложения
-	if app, err = appInit("config.json"); err != nil {
+	if app, err = models.AppInit("config.json"); err != nil {
 		log.Fatal(err)
 	}
 
 	// Настройка маршрутов и запуск
-	if app.setupRouterAndRun() != nil {
+	if setupRoutesAndRun(app) != nil {
 		log.Fatal(err)
 	}
 }
 
-// Запускает сервер на указанном IP и порту
-func runServer(ip, port string, router http.Handler) {
-	addr := ip + port
-	log.Println("Run on", addr)
-	log.Fatal(http.ListenAndServe(addr, router))
-}
-
-// Инициализирует приложение
-func appInit(configPath string) (*app, error) {
-
-	a := &app{
-		startTime: time.Now().Unix(),
-		config:    config.Init(),
-		cache:     cache.Init(),
-	}
-
-	// Загрузка конфига
-	if err := a.config.Load(configPath); err != nil {
-		return nil, err
-	}
-
-	// Загрузка шаблонов
-	if err := a.loadTemplates(a.config.TemplatesPath, a.config.TemplatesExt); err != nil {
-		log.Fatal(err)
-	}
-
-	return a, nil
-}
-
-func (a *app) setupRouterAndRun() error {
+func setupRoutesAndRun(a *models.App) error {
 	// Настройка маршрутов
-	router := a.setupRouter()
+	router := setupRoutes(a)
 
 	// Запуск сервера
-	if ok, err := tools.IsIPInUse(a.config.ServerIP); err != nil {
+	if ok, err := tools.IsIPInUse(a.Config.ServerIP); err != nil {
 		return err
 	} else if ok {
-		runServer(a.config.ServerIP, a.config.Port, router)
+		runServer(a.Config.ServerIP, a.Config.Port, router)
 	} else {
-		runServer(a.config.LocalIP, a.config.Port, router)
+		runServer(a.Config.LocalIP, a.Config.Port, router)
 	}
 
 	return nil
 }
 
 // Настраивает маршруты
-func (a *app) setupRouter() *http.ServeMux {
+func setupRoutes(a *models.App) *http.ServeMux {
 	router := http.NewServeMux()
 
 	// Обработка статических файлов с кэшированием
-	router.Handle(a.config.AssetsPath, a.staticHandler())
+	router.Handle(a.Config.AssetsPath, controllers.StaticHandler())
 
 	// Обработка главной страницы
-	router.Handle("/", a.mainPageHandler())
+	router.Handle("/", controllers.MainPageHandler(a))
 
 	return router
 }
 
-// Обработчик статических файлов с кэшированием
-func (a *app) staticHandler() http.HandlerFunc {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Add("Cache-Control", "public, max-age=31536000, immutable")
-		// Здесь используется встроенный файловый сервер Go (http.FileServer), который:
-		// Реализует интерфейс http.Handler (и поэтому имеет метод ServeHTTP)
-		// Автоматически обслуживает статические файлы из файловой системы
-		// Сам обрабатывает HTTP-запросы, определяет MIME-типы, отправляет правильные заголовки и т.д.
-		http.FileServer(http.Dir(".")).ServeHTTP(w, r)
-	})
-}
-
-// Обработчик главной страницы
-func (a *app) mainPageHandler() http.HandlerFunc {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		var err error
-		tmplName := "main" + a.config.TemplatesExt
-
-		// Страничка рендерится только если ее нет в кэше
-		pageData, ok := a.cache.Get(tmplName)
-		if !ok {
-			context := map[string]any{
-				"version":            a.startTime,
-				"renderingTimestamp": time.Now().Unix(),
-			}
-			pageData, err = a.renderPage(tmplName, context)
-			if err != nil {
-				http.Error(w, err.Error(), http.StatusInternalServerError)
-				return
-			}
-			a.cache.Set(tmplName, pageData)
-		}
-
-		a.sendPage(w, pageData.([]byte))
-	})
-}
-
-// Загрузка шаблонов
-func (a *app) loadTemplates(templatesPath string, ext string) error {
-	tmpls := template.New("")
-
-	err := filepath.Walk(templatesPath, func(path string, f os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-		if !f.IsDir() && strings.HasSuffix(f.Name(), ext) {
-			_, err = tmpls.ParseFiles(path)
-			if err != nil {
-				return err
-			}
-		}
-		return nil
-	})
-
-	if err != nil {
-		return err
-	}
-
-	a.templates = tmpls
-	return nil
-}
-
-// Рендерит шаблон в срез байт
-func (a *app) renderPage(tmplName string, context any) ([]byte, error) {
-	var pageData bytes.Buffer
-
-	if err := a.templates.ExecuteTemplate(&pageData, tmplName, context); err != nil {
-		return nil, err
-	}
-
-	return pageData.Bytes(), nil
-}
-
-// Отправляет страницу
-func (a *app) sendPage(w http.ResponseWriter, data []byte) {
-	w.Header().Set("Content-Type", "text/html; charset=utf-8")
-	w.WriteHeader(http.StatusOK)
-	w.Write(data)
+// Обертка над ListenAndServe, запускает сервер на указанном IP, PORT
+func runServer(ip, port string, router http.Handler) {
+	addr := ip + port
+	log.Println("Run on", addr)
+	log.Fatal(http.ListenAndServe(addr, router))
 }
diff --git a/mvc/controllers/main_page.go b/mvc/controllers/main_page.go
new file mode 100644
index 0000000..1e78e3c
--- /dev/null
+++ b/mvc/controllers/main_page.go
@@ -0,0 +1,33 @@
+package controllers
+
+import (
+	"main/mvc/models"
+	"net/http"
+)
+
+// Обработчик главной страницы
+func MainPageHandler(a *models.App) http.HandlerFunc {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		var err error
+
+		// Страничка рендерится только если ее нет в кэше
+		pageData, ok := a.Cache.Get(models.MainPageTmplName)
+		if !ok {
+			pageData, err = a.RenderMainPage()
+			if err != nil {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+				return
+			}
+			a.Cache.Set(models.MainPageTmplName, pageData)
+		}
+
+		SendMainPage(w, pageData.([]byte))
+	})
+}
+
+// Отправляет страницу
+func SendMainPage(w http.ResponseWriter, data []byte) {
+	w.Header().Set("Content-Type", "text/html; charset=utf-8")
+	w.WriteHeader(http.StatusOK)
+	w.Write(data)
+}
diff --git a/mvc/controllers/static.go b/mvc/controllers/static.go
new file mode 100644
index 0000000..93806fd
--- /dev/null
+++ b/mvc/controllers/static.go
@@ -0,0 +1,15 @@
+package controllers
+
+import "net/http"
+
+// Обработчик статических файлов с кэшированием
+func StaticHandler() http.HandlerFunc {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Add("Cache-Control", "public, max-age=31536000, immutable")
+		// Здесь используется встроенный файловый сервер Go (http.FileServer), который:
+		// Реализует интерфейс http.Handler (и поэтому имеет метод ServeHTTP)
+		// Автоматически обслуживает статические файлы из файловой системы
+		// Сам обрабатывает HTTP-запросы, определяет MIME-типы, отправляет правильные заголовки и т.д.
+		http.FileServer(http.Dir(".")).ServeHTTP(w, r)
+	})
+}
diff --git a/mvc/models/app.go b/mvc/models/app.go
new file mode 100644
index 0000000..b4a2fcf
--- /dev/null
+++ b/mvc/models/app.go
@@ -0,0 +1,65 @@
+package models
+
+import (
+	"html/template"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+)
+
+// App хранит информацию о приложении
+type App struct {
+	Config    *Config            // Конфиг
+	Templates *template.Template // Шаблоны страниц
+	Cache     *Cache             // Кэш (отрендеренные странички)
+	StartTime int64              // Время запуска
+}
+
+// Инициализирует приложение
+func AppInit(configPath string) (*App, error) {
+
+	a := &App{
+		StartTime: time.Now().Unix(),
+		Config:    ConfigInit(),
+		Cache:     CacheInit(),
+	}
+
+	// Загрузка конфига
+	if err := a.Config.Load(configPath); err != nil {
+		return nil, err
+	}
+
+	// Загрузка шаблонов
+	if err := a.loadTemplates(a.Config.TemplatesPath, a.Config.TemplatesExt); err != nil {
+		log.Fatal(err)
+	}
+
+	return a, nil
+}
+
+// Загрузка шаблонов
+func (a *App) loadTemplates(templatesPath string, ext string) error {
+	tmpls := template.New("")
+
+	err := filepath.Walk(templatesPath, func(path string, f os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		if !f.IsDir() && strings.HasSuffix(f.Name(), ext) {
+			_, err = tmpls.ParseFiles(path)
+			if err != nil {
+				return err
+			}
+		}
+		return nil
+	})
+
+	if err != nil {
+		return err
+	}
+
+	a.Templates = tmpls
+	return nil
+}
diff --git a/cache/cache.go b/mvc/models/cache.go
similarity index 89%
rename from cache/cache.go
rename to mvc/models/cache.go
index 9c5a616..5554aa6 100644
--- a/cache/cache.go
+++ b/mvc/models/cache.go
@@ -1,4 +1,4 @@
-package cache
+package models
 
 import "sync"
 
@@ -7,7 +7,7 @@ type Cache struct {
 	Mu   sync.RWMutex
 }
 
-func Init() *Cache {
+func CacheInit() *Cache {
 	return &Cache{Data: make(map[string]any)}
 }
 
diff --git a/config/config.go b/mvc/models/config.go
similarity index 90%
rename from config/config.go
rename to mvc/models/config.go
index 68d5d78..162db30 100644
--- a/config/config.go
+++ b/mvc/models/config.go
@@ -1,4 +1,4 @@
-package config
+package models
 
 import (
 	"encoding/json"
@@ -14,7 +14,7 @@ type Config struct {
 	Port          string
 }
 
-func Init() *Config {
+func ConfigInit() *Config {
 	return &Config{}
 }
 
diff --git a/mvc/models/main_page.go b/mvc/models/main_page.go
new file mode 100644
index 0000000..63ee2d7
--- /dev/null
+++ b/mvc/models/main_page.go
@@ -0,0 +1,26 @@
+package models
+
+import (
+	"bytes"
+	"time"
+)
+
+const (
+	// Имя соответствующего шаблона
+	MainPageTmplName = "main_page.gohtml"
+)
+
+func (a *App) RenderMainPage() ([]byte, error) {
+	var pageData bytes.Buffer
+
+	context := map[string]any{
+		"version":            a.StartTime,
+		"renderingTimestamp": time.Now().Unix(),
+	}
+
+	if err := a.Templates.ExecuteTemplate(&pageData, MainPageTmplName, context); err != nil {
+		return nil, err
+	}
+
+	return pageData.Bytes(), nil
+}
diff --git a/assets/templates/main.gohtml b/mvc/views/main_page.gohtml
similarity index 100%
rename from assets/templates/main.gohtml
rename to mvc/views/main_page.gohtml