posts
serr 2025-04-11 11:12:38 +03:00
parent b68851c862
commit cb0358dbb4
10 changed files with 115 additions and 124 deletions

50
main.go
View File

@ -6,75 +6,57 @@ import (
"main/mvc/controllers" "main/mvc/controllers"
"main/mvc/controllers/controllers_pages" "main/mvc/controllers/controllers_pages"
"main/mvc/models" "main/mvc/models"
"main/mvc/models/models_pages"
"main/tools" "main/tools"
"net/http" "net/http"
) )
func main() { func main() {
var err error var err error
// Инициализация конфига var app *models.App
if err := models.Cfg.Load("config.json"); err != nil {
log.Fatal(err)
}
// Загрузка постов // Инициализация приложения
if err := models_pages.LoadPosts(models.Cfg.PostsDir); err != nil { if app, err = models.InitApp(); err != nil {
log.Fatal(err)
}
// Инициализация приложения (обязательно после инициализации конфига)
if err := models.InitApp(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
// Добавление префикса в виде домена сервера к записям в лог // Добавление префикса в виде домена сервера к записям в лог
log.SetPrefix(fmt.Sprintf("%s | ", models.Cfg.ServerDomain)) log.SetPrefix(fmt.Sprintf("%s | ", app.Cfg.ServerDomain))
// Настройка маршрутов и запуск
if setupRoutesAndRun() != nil {
log.Fatal(err)
}
}
func setupRoutesAndRun() error {
// Настройка маршрутов // Настройка маршрутов
router := setupRoutes() router := setupRoutes(app)
// Запуск сервера // Запуск сервера
if ok, err := tools.IsIPInUse(models.Cfg.ServerIP); err != nil { if ok, err := tools.IsIPInUse(app.Cfg.ServerIP); err != nil {
return err log.Fatal(err)
} else if ok { } else if ok {
runServer(models.Cfg.ServerIP, models.Cfg.ServerPort, router) runServer(app.Cfg.ServerIP, app.Cfg.ServerPort, router)
} else { } else {
runServer(models.Cfg.LocalIP, models.Cfg.LocalPort, router) runServer(app.Cfg.LocalIP, app.Cfg.LocalPort, router)
} }
return nil
} }
// Настраивает маршруты // Настраивает маршруты
func setupRoutes() *http.ServeMux { func setupRoutes(app *models.App) *http.ServeMux {
router := http.NewServeMux() router := http.NewServeMux()
// Цепочка обработчиков, которые сработают до отдачи страницы юзеру // Цепочка обработчиков, которые сработают до отдачи страницы юзеру
m := controllers.MiddlewaresChain m := controllers.MiddlewaresChain
// Обработка статических файлов // Обработка статических файлов
router.Handle(models.Cfg.AssetsDir, m(controllers.StaticHandler())) router.Handle(app.Cfg.AssetsDir, m(controllers.StaticHandler()))
// Главные странички // Главные странички
{ {
// Обработка главной страницы (русская версия) // Обработка главной страницы (русская версия)
router.Handle("/ru/", m(controllers_pages.MainRuPageHandler())) router.Handle("/ru/", m(controllers_pages.MainRuPageHandler(app)))
// Обработка главной страницы // Обработка главной страницы
router.Handle("/", m(controllers_pages.MainPageHandler())) router.Handle("/", m(controllers_pages.MainPageHandler(app)))
// Обработка страницы со списком постов // Обработка страницы со списком постов
router.Handle("/posts/", m(controllers_pages.PostsPageHandler())) router.Handle("/posts/", m(controllers_pages.PostsPageHandler(app)))
// Обработка страничек постов // Обработка страничек постов
for key := range models_pages.GetPosts() { for key := range app.Posts {
postLink := string(key) postLink := string(key)
router.Handle(postLink, m(controllers_pages.PostPageHandler())) router.Handle(postLink, m(controllers_pages.PostPageHandler(app)))
} }
} }

View File

@ -10,7 +10,7 @@ import (
) )
// Обработчик главной страницы // Обработчик главной страницы
func MainPageHandler() http.HandlerFunc { func MainPageHandler(app *models.App) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error var err error
@ -18,7 +18,7 @@ func MainPageHandler() http.HandlerFunc {
// Количество запросов, обработанных сервером за 24ч // Количество запросов, обработанных сервером за 24ч
if r.Method == "COUNT" { if r.Method == "COUNT" {
var count []byte var count []byte
if count, err = tools.GetJournalctlLogsCount("server", models.Cfg.ServerDomain, 24); err != nil { if count, err = tools.GetJournalctlLogsCount("server", app.Cfg.ServerDomain, 24); err != nil {
log.Printf("%s", err.Error()) log.Printf("%s", err.Error())
} }
sendCount(w, count) sendCount(w, count)
@ -38,14 +38,14 @@ func MainPageHandler() http.HandlerFunc {
} }
// Страничка рендерится только если ее нет в кэше // Страничка рендерится только если ее нет в кэше
pageData, ok := models.PagesCache.Get(models_pages.MainPageTmplName) pageData, ok := app.PagesCache.Get(models_pages.MainPageTmplName)
if !ok { if !ok {
pageData, err = models_pages.RenderMainPage(models.App.Templates, models.App.Version) pageData, err = models_pages.RenderMainPage(app.Templates, app.Version)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
models.PagesCache.Set(models_pages.MainPageTmplName, pageData) app.PagesCache.Set(models_pages.MainPageTmplName, pageData)
} }
sendMainPage(w, pageData.([]byte)) sendMainPage(w, pageData.([]byte))

View File

@ -7,19 +7,19 @@ import (
) )
// Обработчик главной страницы // Обработчик главной страницы
func MainRuPageHandler() http.HandlerFunc { func MainRuPageHandler(app *models.App) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error var err error
// Страничка рендерится только если ее нет в кэше // Страничка рендерится только если ее нет в кэше
pageData, ok := models.PagesCache.Get(models_pages.MainRuPageTmplName) pageData, ok := app.PagesCache.Get(models_pages.MainRuPageTmplName)
if !ok { if !ok {
pageData, err = models_pages.RenderMainRuPage(models.App.Templates, models.App.Version) pageData, err = models_pages.RenderMainRuPage(app.Templates, app.Version)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
models.PagesCache.Set(models_pages.MainRuPageTmplName, pageData) app.PagesCache.Set(models_pages.MainRuPageTmplName, pageData)
} }
sendMainRuPage(w, pageData.([]byte)) sendMainRuPage(w, pageData.([]byte))

View File

@ -7,24 +7,22 @@ import (
) )
// Обработчик главной страницы // Обработчик главной страницы
func PostPageHandler() http.HandlerFunc { func PostPageHandler(app *models.App) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error var err error
posts := models_pages.GetPosts()
// Страничка рендерится только если ее нет в кэше // Страничка рендерится только если ее нет в кэше
pageData, ok := models.PagesCache.Get(models_pages.PostPageTmplName) pageData, ok := app.PagesCache.Get(models_pages.PostPageTmplName)
if !ok { if !ok {
post := posts[models_pages.PostLink(r.URL.Path)] post := app.Posts[models_pages.PostLink(r.URL.Path)]
pageData, err = models_pages.RenderPostPage(models.App.Templates, models.App.Version, post.Data) pageData, err = models_pages.RenderPostPage(app.Templates, app.Version, post.Data)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
models.PagesCache.Set(models_pages.PostPageTmplName, pageData) app.PagesCache.Set(models_pages.PostPageTmplName, pageData)
} }
sendPostPage(w, pageData.([]byte)) sendPostPage(w, pageData.([]byte))

View File

@ -7,19 +7,19 @@ import (
) )
// Обработчик главной страницы // Обработчик главной страницы
func PostsPageHandler() http.HandlerFunc { func PostsPageHandler(app *models.App) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error var err error
// Страничка рендерится только если ее нет в кэше // Страничка рендерится только если ее нет в кэше
pageData, ok := models.PagesCache.Get(models_pages.PostsPageTmplName) pageData, ok := app.PagesCache.Get(models_pages.PostsPageTmplName)
if !ok { if !ok {
pageData, err = models_pages.RenderPostsPage(models.App.Templates, models.App.Version) pageData, err = app.Posts.RenderPostsPage(app.Templates, app.Version)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
models.PagesCache.Set(models_pages.PostsPageTmplName, pageData) app.PagesCache.Set(models_pages.PostsPageTmplName, pageData)
} }
sendPostsPage(w, pageData.([]byte)) sendPostsPage(w, pageData.([]byte))

View File

@ -2,55 +2,39 @@ package models
import ( import (
"html/template" "html/template"
"os" "main/mvc/models/models_pages"
"path/filepath"
"strings"
"time" "time"
) )
// App хранит шаблоны и время запуска type App struct {
type app struct { Cfg *Config // Сонфиг
Posts models_pages.Posts // Посты
Templates *template.Template // Шаблоны страниц Templates *template.Template // Шаблоны страниц
PagesCache *Cache // Кэш (отрендеренные странички)
Version int64 // Время запуска Version int64 // Время запуска
} }
var (
App = &app{}
)
// Инициализирует приложение // Инициализирует приложение
func InitApp() error { func InitApp() (*App, error) {
var err error
App.Version = time.Now().Unix() app := &App{}
// Версия чтобы статика не кэшировалась
app.Version = time.Now().Unix()
// Загрузка конфига
if app.Cfg, err = loadConfig(ConfigPath); err != nil {
return nil, err
}
// Загрузка постов
if app.Posts, err = models_pages.LoadPosts(app.Cfg.PostsDir); err != nil {
return nil, err
}
// Загрузка шаблонов // Загрузка шаблонов
if err := App.loadTemplates(Cfg.TemplatesDir, Cfg.TemplatesExt); err != nil { if app.Templates, err = loadTemplates(app.Cfg.TemplatesDir, app.Cfg.TemplatesExt); err != nil {
return err return nil, err
} }
return nil // Инициализация кэша
} app.PagesCache = initCache()
return app, 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
} }

View File

@ -2,23 +2,23 @@ package models
import "sync" import "sync"
type cache struct { type Cache struct {
Data map[string]any Data map[string]any
Mu sync.RWMutex Mu sync.RWMutex
} }
var ( func initCache() *Cache {
PagesCache = cache{Data: make(map[string]any)} return &Cache{Data: make(map[string]any)}
) }
func (c *cache) Get(key string) (any, bool) { func (c *Cache) Get(key string) (any, bool) {
c.Mu.RLock() c.Mu.RLock()
pageData, ok := c.Data[key] pageData, ok := c.Data[key]
c.Mu.RUnlock() c.Mu.RUnlock()
return pageData, ok return pageData, ok
} }
func (c *cache) Set(key string, data any) { func (c *Cache) Set(key string, data any) {
c.Mu.Lock() c.Mu.Lock()
c.Data[key] = data c.Data[key] = data
c.Mu.Unlock() c.Mu.Unlock()

View File

@ -5,7 +5,11 @@ import (
"os" "os"
) )
type config struct { const (
ConfigPath = "config.json"
)
type Config struct {
PostsDir string PostsDir string
AssetsDir string AssetsDir string
TemplatesDir string TemplatesDir string
@ -18,18 +22,15 @@ type config struct {
Port string Port string
} }
var ( func loadConfig(configPath string) (*Config, error) {
Cfg = &config{} cfg := &Config{}
)
func (c *config) Load(configPath string) error {
configFile, err := os.ReadFile(configPath) configFile, err := os.ReadFile(configPath)
if err != nil { if err != nil {
return err return nil, err
} }
err = json.Unmarshal(configFile, c) err = json.Unmarshal(configFile, cfg)
if err != nil { if err != nil {
return err return nil, err
} }
return nil return cfg, nil
} }

View File

@ -17,17 +17,12 @@ const (
PostsPageTmplName = "posts.gohtml" PostsPageTmplName = "posts.gohtml"
) )
type posts map[PostLink]*Post type Posts map[PostLink]*Post
var ( func LoadPosts(dir string) (Posts, error) {
allPosts = posts{}
)
func GetPosts() posts { posts := Posts{}
return allPosts
}
func LoadPosts(dir string) error {
err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err
@ -46,25 +41,25 @@ func LoadPosts(dir string) error {
html := tools.MdToHTML(md) html := tools.MdToHTML(md)
link := fmt.Sprintf("/%s/", strings.TrimSuffix(filepath.Base(path), ".md")) link := fmt.Sprintf("/%s/", strings.TrimSuffix(filepath.Base(path), ".md"))
allPosts[PostLink(link)] = newPost(link, html) posts[PostLink(link)] = newPost(link, html)
} }
return nil return nil
}) })
if err != nil { if err != nil {
return err return nil, err
} }
return nil return posts, nil
} }
func RenderPostsPage(templates *template.Template, version int64) ([]byte, error) { func (p *Posts) RenderPostsPage(templates *template.Template, version int64) ([]byte, error) {
var pageData bytes.Buffer var pageData bytes.Buffer
context := map[string]any{ context := map[string]any{
"version": version, "version": version,
"renderingTimestamp": time.Now().Unix(), "renderingTimestamp": time.Now().Unix(),
"posts": allPosts, "posts": p,
} }
if err := templates.ExecuteTemplate(&pageData, PostsPageTmplName, context); err != nil { if err := templates.ExecuteTemplate(&pageData, PostsPageTmplName, context); err != nil {

31
mvc/models/templates.go Normal file
View File

@ -0,0 +1,31 @@
package models
import (
"html/template"
"os"
"path/filepath"
"strings"
)
func loadTemplates(templatesPath string, ext string) (*template.Template, 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 nil, err
}
return tmpls, nil
}