/ gibson.go
gibson.go
  1  package main
  2  
  3  import (
  4  	"bytes"
  5  	"flag"
  6  	"fmt"
  7  	"html/template"
  8  	"io"
  9  	"log"
 10  	"net/http"
 11  	"os"
 12  	"strconv"
 13  	"strings"
 14  	"time"
 15  
 16  	utils "gibson/internal"
 17  
 18  	"github.com/BurntSushi/toml"
 19  	"github.com/fsnotify/fsnotify"
 20  	"github.com/labstack/echo/v4"
 21  	"github.com/labstack/echo/v4/middleware"
 22  )
 23  
 24  const Version = "0.7.4"
 25  
 26  var ConfigFile string
 27  var config utils.TomlConfig
 28  var feedrss []utils.FeedRss
 29  var rssChannel chan []utils.FeedRss
 30  var pinnedposts []utils.Pinned
 31  var links []utils.Link
 32  
 33  type Template struct {
 34  	templates *template.Template
 35  }
 36  
 37  // https://stackoverflow.com/questions/72929883/golang-echo-labstack-how-to-call-a-function-method-in-a-template-view
 38  func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
 39  
 40  	// Add global methods if data is a map
 41  	if viewContext, isMap := data.(map[string]interface{}); isMap {
 42  		viewContext["reverse"] = c.Echo().Reverse
 43  	}
 44  
 45  	return t.templates.ExecuteTemplate(w, name, data)
 46  }
 47  
 48  func customHTTPErrorHandler(err error, c echo.Context) {
 49  	code := http.StatusInternalServerError
 50  	if he, ok := err.(*echo.HTTPError); ok {
 51  		code = he.Code
 52  	}
 53  	c.Logger().Error(err)
 54  	errorPage := fmt.Sprintf(config.Gibson.Directory+"templates/%d.html", code)
 55  	if err := c.File(errorPage); err != nil {
 56  		c.Logger().Error(err)
 57  	}
 58  }
 59  
 60  // https://stackoverflow.com/questions/30065203/golang-html-templating-range-limit
 61  func mkslice(a []utils.BlogPost, start, end int) []utils.BlogPost {
 62  	return a[start:end]
 63  }
 64  
 65  func reloadMarkdown(c echo.Context) error {
 66  	var template Template
 67  	w := c.Response()
 68  	w.Header().Set("Content-Type", "text/event-stream")
 69  	w.Header().Set("Cache-Control", "no-cache")
 70  	w.Header().Set("Connection", "keep-alive")
 71  
 72  	// Create new watcher.
 73  	watcher, err := fsnotify.NewWatcher()
 74  	if err != nil {
 75  		log.Fatal(err)
 76  	}
 77  	defer watcher.Close()
 78  
 79  	// Add a path.
 80  	err = watcher.Add(config.Gibson.Directory + "markdown/posts")
 81  	if err != nil {
 82  		log.Fatal(err)
 83  	}
 84  
 85  	// Start listening for events.
 86  	for {
 87  		select {
 88  		case <-c.Request().Context().Done():
 89  			//log.Printf("SSE client disconnected, ip: %v", c.RealIP())
 90  			return nil
 91  
 92  		case event, ok := <-watcher.Events:
 93  			if !ok {
 94  				return nil
 95  			}
 96  			//log.Println("event:", event)
 97  			if event.Has(fsnotify.Write) {
 98  				//log.Println("modified file:", event.Name)
 99  
100  				payload, err := utils.LoadMarkdownPost(config.Gibson.Directory + event.Name)
101  				if err != nil {
102  					fmt.Println(err)
103  					return nil
104  				}
105  				var parse bytes.Buffer
106  				parsedTemplate, _ := template.templates.ParseFiles(config.Gibson.Directory + "templates/content_reload.html")
107  				parsedTemplate.Execute(&parse, payload)
108  
109  				event := utils.Event{
110  					Data: []byte(parse.Bytes()),
111  				}
112  				if err := event.MarshalTo(w); err != nil {
113  					return err
114  				}
115  				w.Flush()
116  			}
117  		case err, ok := <-watcher.Errors:
118  			if !ok {
119  				return nil
120  			}
121  			log.Println("error:", err)
122  		}
123  	}
124  
125  }
126  
127  func httpServer() {
128  	e := echo.New()
129  	//e.Debug = true
130  	e.Use(middleware.Logger())
131  	mc := utils.NewMediaController(config.S3)
132  	e.GET("/media/:id", mc.ExtractFromS3)
133  
134  	s := &http.Server{
135  		Addr:         "127.0.0.1:8079",
136  		ReadTimeout:  60 * time.Minute,
137  		WriteTimeout: 60 * time.Minute,
138  	}
139  	fmt.Println("HttpServer START")
140  	e.Logger.Fatal(e.StartServer(s))
141  }
142  
143  func handleSSE(c echo.Context) error {
144  	//log.Printf("SSE client connected, ip: %v", c.RealIP())
145  
146  	w := c.Response()
147  	w.Header().Set("Content-Type", "text/event-stream")
148  	w.Header().Set("Cache-Control", "no-cache")
149  	w.Header().Set("Connection", "keep-alive")
150  
151  	var template Template
152  	var payload utils.Payload
153  
154  	for {
155  		select {
156  		case <-c.Request().Context().Done():
157  			//log.Printf("SSE client disconnected, ip: %v", c.RealIP())
158  			return nil
159  
160  		case feedrss := <-rssChannel:
161  			payload.FeedRss = feedrss
162  			var parse bytes.Buffer
163  			parsedTemplate, _ := template.templates.ParseFiles(config.Gibson.Directory + "templates/rss_reload.html")
164  			parsedTemplate.Execute(&parse, payload)
165  
166  			event := utils.Event{
167  				Data: []byte(parse.Bytes()),
168  			}
169  			if err := event.MarshalTo(w); err != nil {
170  				return err
171  			}
172  			w.Flush()
173  		}
174  	}
175  }
176  
177  // ServerHeader middleware adds a `AccessControlAllowOrigin` header to the response.
178  func ServerHeader(next echo.HandlerFunc) echo.HandlerFunc {
179  	return func(c echo.Context) error {
180  		c.Response().Header().Set(echo.HeaderAccessControlAllowOrigin, "*")
181  		return next(c)
182  	}
183  }
184  
185  func loadMarkdown(e *echo.Echo) error {
186  
187  	// external data
188  	fmt.Println("before external")
189  	externalData, err := utils.LoadExternalData(config.Gibson.Directory + "markdown/external/")
190  	if err != nil {
191  		log.Fatal(err)
192  	}
193  	fmt.Println("after external")
194  	// pages data
195  	pagesData, err := utils.LoadData(config.Gibson.Directory + "markdown/pages/")
196  	if err != nil {
197  		log.Fatal(err)
198  	}
199  
200  	// textes data
201  	textesData, err := utils.LoadData(config.Gibson.Directory + "markdown/textes/")
202  	if err != nil {
203  		log.Fatal(err)
204  	}
205  
206  	// posts data
207  	postsData, err := utils.LoadData(config.Gibson.Directory + "markdown/posts/")
208  	if err != nil {
209  		log.Fatal(err)
210  	}
211  
212  	// parse pinned articles
213  	//
214  	pinnedposts = nil
215  	for _, article := range config.Pinned {
216  		l_pinnedposts := utils.Pinned{Name: article.Name, URL: article.URL}
217  		pinnedposts = append(pinnedposts, l_pinnedposts)
218  	}
219  
220  	// parse links
221  	//
222  	links = nil
223  	for _, link := range config.Link {
224  		l_links := utils.Link{Name: link.Name, URL: link.URL}
225  		links = append(links, l_links)
226  	}
227  
228  	// generate RSS from markdown/posts/
229  	err = utils.RssGenerate(postsData.Categories[0].Pages, config)
230  	if err != nil {
231  		log.Fatal(err)
232  	}
233  
234  	e.GET("/tags/:id", func(c echo.Context) error {
235  		tagsData := utils.BuildData(c.Param("id"), postsData)
236  
237  		indexPath := config.Gibson.Directory + "markdown/posts/index.md"
238  		indexContent, err := os.ReadFile(indexPath)
239  		if err != nil {
240  			c.Logger().Error(err)
241  			return err
242  		}
243  
244  		post, err := utils.ParseMarkdownFile(indexContent)
245  		if err != nil {
246  			c.Logger().Error(err)
247  			return err
248  		}
249  		sidebarLinks := utils.CreateSidebarLinks(post.Headers)
250  
251  		var nbpages int
252  		if tagsData.Categories[0].NbPosts > 4 {
253  			nbpages = (tagsData.Categories[0].NbPosts / 5) + 1
254  			tagsData.Categories[0].NBpages = 5
255  		} else {
256  			nbpages = tagsData.Categories[0].NbPosts
257  			tagsData.Categories[0].NBpages = nbpages
258  		}
259  
260  		var payload utils.Payload
261  		payload.BlogTitle = config.Blog.Title
262  		payload.BlogDesc = config.Blog.Desc
263  		payload.BlogCopyright = config.Blog.Copyright
264  		payload.Version = Version
265  		payload.FeedRss = feedrss
266  		payload.PinnedPosts = pinnedposts
267  		payload.Links = links
268  		payload.Title = post.Title
269  		payload.Date = post.Date.Format(time.RFC1123)
270  		payload.Content = post.Content
271  		payload.ExternalData = externalData
272  		payload.PagesData = pagesData
273  		payload.PostsData = tagsData
274  		payload.TextesData = textesData
275  		payload.PostsData.Categories[0].CurrentPage = 1
276  		payload.Headers = post.Headers
277  		payload.SidebarLinks = sidebarLinks
278  		payload.CurrentSlug = post.Slug
279  		payload.MetaDescription = post.MetaDescription
280  		payload.MetaPropertyTitle = post.MetaPropertyTitle
281  		payload.MetaPropertyDescription = post.MetaPropertyDescription
282  		payload.MetaOgURL = post.MetaOgURL
283  
284  		return c.Render(http.StatusOK, "tagsindex", payload)
285  	})
286  
287  	e.GET("/", func(c echo.Context) error {
288  		indexPath := config.Gibson.Directory + "markdown/posts/index.md"
289  		indexContent, err := os.ReadFile(indexPath)
290  		if err != nil {
291  			c.Logger().Error(err)
292  			return err
293  		}
294  
295  		post, err := utils.ParseMarkdownFile(indexContent)
296  		if err != nil {
297  			c.Logger().Error(err)
298  			return err
299  		}
300  
301  		sidebarLinks := utils.CreateSidebarLinks(post.Headers)
302  
303  		var nbpages int
304  		if postsData.Categories[0].NbPosts > 4 {
305  			nbpages = (postsData.Categories[0].NbPosts / 5) + 1
306  			postsData.Categories[0].NBpages = 5
307  		} else {
308  			nbpages = postsData.Categories[0].NbPosts
309  			postsData.Categories[0].NBpages = nbpages
310  		}
311  
312  		var payload utils.Payload
313  		payload.BlogTitle = config.Blog.Title
314  		payload.BlogDesc = config.Blog.Desc
315  		payload.BlogCopyright = config.Blog.Copyright
316  		payload.Version = Version
317  		payload.FeedRss = feedrss
318  		payload.PinnedPosts = pinnedposts
319  		payload.Links = links
320  		payload.Title = post.Title
321  		payload.Date = post.Date.Format(time.RFC1123)
322  		payload.Content = post.Content
323  		payload.ExternalData = externalData
324  		payload.PagesData = pagesData
325  		payload.PostsData = postsData
326  		payload.TextesData = textesData
327  		payload.PostsData.Categories[0].CurrentPage = 1
328  		payload.Headers = post.Headers
329  		payload.SidebarLinks = sidebarLinks
330  		payload.CurrentSlug = post.Slug
331  		payload.MetaDescription = post.MetaDescription
332  		payload.MetaPropertyTitle = post.MetaPropertyTitle
333  		payload.MetaPropertyDescription = post.MetaPropertyDescription
334  		payload.MetaOgURL = post.MetaOgURL
335  
336  		return c.Render(http.StatusOK, "index", payload)
337  	})
338  
339  	e.GET("/external", func(c echo.Context) error {
340  		indexPath := config.Gibson.Directory + "markdown/external/index.md"
341  		indexContent, err := os.ReadFile(indexPath)
342  		if err != nil {
343  			c.Logger().Error(err)
344  			return err
345  		}
346  
347  		post, err := utils.ParseExternalMarkdownFile(indexContent)
348  		if err != nil {
349  			c.Logger().Error(err)
350  			return err
351  		}
352  		sidebarLinks := utils.CreateSidebarLinks(post.Headers)
353  
354  		var payload utils.Payload
355  		payload.BlogTitle = config.Blog.Title
356  		payload.BlogDesc = config.Blog.Desc
357  		payload.BlogCopyright = config.Blog.Copyright
358  		payload.Version = Version
359  		payload.FeedRss = feedrss
360  		payload.PinnedPosts = pinnedposts
361  		payload.Links = links
362  		payload.Title = post.Title
363  		payload.Date = post.Published.Format(time.RFC1123)
364  		payload.Content = post.Content
365  		payload.ExternalData = externalData
366  		payload.PagesData = pagesData
367  		payload.PostsData = postsData
368  		payload.TextesData = textesData
369  		payload.PostsData.Categories[0].CurrentPage = 1
370  		payload.Headers = post.Headers
371  		payload.SidebarLinks = sidebarLinks
372  		payload.CurrentSlug = post.Slug
373  
374  		return c.Render(http.StatusOK, "external", payload)
375  	})
376  
377  	e.GET("/textes", func(c echo.Context) error {
378  		indexPath := config.Gibson.Directory + "markdown/textes/index.md"
379  		indexContent, err := os.ReadFile(indexPath)
380  		if err != nil {
381  			c.Logger().Error(err)
382  			return err
383  		}
384  
385  		post, err := utils.ParseMarkdownFile(indexContent)
386  		if err != nil {
387  			c.Logger().Error(err)
388  			return err
389  		}
390  		sidebarLinks := utils.CreateSidebarLinks(post.Headers)
391  
392  		var nbpages int
393  		if textesData.Categories[0].NbPosts > 4 {
394  			nbpages = (textesData.Categories[0].NbPosts / 5) + 1
395  			textesData.Categories[0].NBpages = 5
396  		} else {
397  			nbpages = textesData.Categories[0].NbPosts
398  			textesData.Categories[0].NBpages = nbpages
399  		}
400  
401  		var payload utils.Payload
402  		payload.BlogTitle = config.Blog.Title
403  		payload.BlogDesc = config.Blog.Desc
404  		payload.BlogCopyright = config.Blog.Copyright
405  		payload.Version = Version
406  		payload.FeedRss = feedrss
407  		payload.PinnedPosts = pinnedposts
408  		payload.Links = links
409  		payload.Title = post.Title
410  		payload.Date = post.Date.Format(time.RFC1123)
411  		payload.Content = post.Content
412  		payload.ExternalData = externalData
413  		payload.PagesData = pagesData
414  		payload.PostsData = postsData
415  		payload.TextesData = textesData
416  		payload.PostsData.Categories[0].CurrentPage = 1
417  		payload.Headers = post.Headers
418  		payload.SidebarLinks = sidebarLinks
419  		payload.CurrentSlug = post.Slug
420  		payload.MetaDescription = post.MetaDescription
421  		payload.MetaPropertyTitle = post.MetaPropertyTitle
422  		payload.MetaPropertyDescription = post.MetaPropertyDescription
423  		payload.MetaOgURL = post.MetaOgURL
424  
425  		return c.Render(http.StatusOK, "textes", payload)
426  	})
427  
428  	e.GET("/page/:id", func(c echo.Context) error {
429  		indexPath := config.Gibson.Directory + "markdown/posts/index.md"
430  		indexContent, err := os.ReadFile(indexPath)
431  		if err != nil {
432  			c.Logger().Error(err)
433  			return err
434  		}
435  
436  		post, err := utils.ParseMarkdownFile(indexContent)
437  		if err != nil {
438  			c.Logger().Error(err)
439  			return err
440  		}
441  		sidebarLinks := utils.CreateSidebarLinks(post.Headers)
442  
443  		index, err := strconv.Atoi(c.Param("id"))
444  		if err != nil {
445  			c.Logger().Error(err)
446  			return echo.NewHTTPError(http.StatusNotFound)
447  		}
448  		if index <= 0 {
449  			index = 1
450  		}
451  		var nbpages int
452  		if postsData.Categories[0].NbPosts > 4 {
453  			nbpages = (postsData.Categories[0].NbPosts / 5) + 1
454  			postsData.Categories[0].NBpages = 5
455  		} else {
456  			nbpages = postsData.Categories[0].NbPosts
457  			postsData.Categories[0].NBpages = nbpages
458  		}
459  		if index > nbpages {
460  			index = nbpages
461  		}
462  
463  		//		fmt.Println("nbpages : ", nbpages)
464  
465  		var payload utils.Payload
466  		payload.BlogTitle = config.Blog.Title
467  		payload.BlogDesc = config.Blog.Desc
468  		payload.BlogCopyright = config.Blog.Copyright
469  		payload.Version = Version
470  		payload.FeedRss = feedrss
471  		payload.PinnedPosts = pinnedposts
472  		payload.Links = links
473  		payload.Title = post.Title
474  		payload.Date = post.Date.Format(time.RFC1123)
475  		payload.Content = post.Content
476  		payload.ExternalData = externalData
477  		payload.PagesData = pagesData
478  		payload.PostsData = postsData
479  		payload.PostsData.Categories[0].CurrentPage = index
480  		payload.TextesData = textesData
481  		payload.Headers = post.Headers
482  		payload.SidebarLinks = sidebarLinks
483  		payload.CurrentSlug = post.Slug
484  		payload.MetaDescription = post.MetaDescription
485  		payload.MetaPropertyTitle = post.MetaPropertyTitle
486  		payload.MetaPropertyDescription = post.MetaPropertyDescription
487  		payload.MetaOgURL = post.MetaOgURL
488  
489  		return c.Render(http.StatusOK, "index", payload)
490  	})
491  
492  	e.GET("/posts", func(c echo.Context) error {
493  		indexPath := config.Gibson.Directory + "markdown/posts/index.md"
494  		indexContent, err := os.ReadFile(indexPath)
495  		if err != nil {
496  			c.Logger().Error(err)
497  			return err
498  		}
499  
500  		post, err := utils.ParseMarkdownFile(indexContent)
501  		if err != nil {
502  			c.Logger().Error(err)
503  			return err
504  		}
505  		sidebarLinks := utils.CreateSidebarLinks(post.Headers)
506  
507  		var payload utils.Payload
508  		payload.BlogTitle = config.Blog.Title
509  		payload.BlogDesc = config.Blog.Desc
510  		payload.BlogCopyright = config.Blog.Copyright
511  		payload.Version = Version
512  		payload.FeedRss = feedrss
513  		payload.PinnedPosts = pinnedposts
514  		payload.Links = links
515  		payload.Title = post.Title
516  		payload.Date = post.Date.Format(time.RFC1123)
517  		payload.Content = post.Content
518  		payload.ExternalData = externalData
519  		payload.PagesData = pagesData
520  		payload.PostsData = postsData
521  		payload.TextesData = textesData
522  		payload.Headers = post.Headers
523  		payload.SidebarLinks = sidebarLinks
524  		payload.CurrentSlug = post.Slug
525  		payload.MetaDescription = post.MetaDescription
526  		payload.MetaPropertyTitle = post.MetaPropertyTitle
527  		payload.MetaPropertyDescription = post.MetaPropertyDescription
528  		payload.MetaOgURL = post.MetaOgURL
529  
530  		return c.Render(http.StatusOK, "posts_index", payload)
531  	})
532  
533  	for _, external := range externalData.Categories[0].Pages {
534  		localPost := external
535  		if localPost.Slug != "" {
536  			sidebarLinks := utils.CreateSidebarLinks(localPost.Headers)
537  			slug := strings.ReplaceAll(localPost.Slug, "\"", "")
538  			e.GET("/external/"+slug+"/", func(c echo.Context) error {
539  				var payload utils.Payload
540  				payload.BlogTitle = config.Blog.Title
541  				payload.BlogDesc = config.Blog.Desc
542  				payload.BlogCopyright = config.Blog.Copyright
543  				payload.Version = Version
544  				payload.FeedRss = feedrss
545  				payload.PinnedPosts = pinnedposts
546  				payload.Links = links
547  				payload.Title = strings.ReplaceAll(localPost.Title, "\"", "")
548  				payload.Date = strings.ReplaceAll(localPost.Published.Format(time.RFC1123), "\"", "")
549  				payload.Content = localPost.Content
550  				payload.ExternalData = externalData
551  				payload.PagesData = pagesData
552  				payload.TextesData = textesData
553  				payload.Headers = localPost.Headers
554  				payload.Description = strings.ReplaceAll(localPost.Description, "\"", "")
555  				payload.SidebarLinks = sidebarLinks
556  				payload.CurrentSlug = strings.ReplaceAll(localPost.Slug, "\"", "")
557  				payload.Source = strings.ReplaceAll(localPost.Source, "\"", "")
558  				payload.Author = strings.ReplaceAll(localPost.Author, "\"", "")
559  
560  				return c.Render(http.StatusOK, "layout_external_page", payload)
561  			})
562  		} else {
563  			log.Printf("Warning: Post titled '%s' has an empty slug and will not be accessible via a unique URL.\n", localPost.Title)
564  		}
565  	}
566  
567  	for _, page := range pagesData.Categories[0].Pages {
568  		localPost := page
569  		if localPost.Slug != "" {
570  			sidebarLinks := utils.CreateSidebarLinks(localPost.Headers)
571  			slug := strings.ReplaceAll(localPost.Slug, "\"", "")
572  			e.GET("/page/"+slug+"/", func(c echo.Context) error {
573  				var payload utils.Payload
574  				payload.BlogTitle = config.Blog.Title
575  				payload.BlogDesc = config.Blog.Desc
576  				payload.BlogCopyright = config.Blog.Copyright
577  				payload.Version = Version
578  				payload.FeedRss = feedrss
579  				payload.PinnedPosts = pinnedposts
580  				payload.Links = links
581  				payload.Title = strings.ReplaceAll(localPost.Title, "\"", "")
582  				payload.Date = strings.ReplaceAll(localPost.Date.Format(time.RFC1123), "\"", "")
583  				payload.Content = localPost.Content
584  				payload.ExternalData = externalData
585  				payload.PagesData = pagesData
586  				payload.TextesData = textesData
587  				payload.Headers = localPost.Headers
588  				payload.Description = strings.ReplaceAll(localPost.Description, "\"", "")
589  				payload.SidebarLinks = sidebarLinks
590  				payload.CurrentSlug = strings.ReplaceAll(localPost.Slug, "\"", "")
591  				payload.MetaDescription = strings.ReplaceAll(localPost.MetaDescription, "\"", "")
592  				payload.MetaPropertyTitle = strings.ReplaceAll(localPost.MetaPropertyTitle, "\"", "")
593  				payload.MetaPropertyDescription = strings.ReplaceAll(localPost.MetaPropertyDescription, "\"", "")
594  				payload.MetaOgURL = strings.ReplaceAll(localPost.MetaOgURL, "\"", "")
595  
596  				return c.Render(http.StatusOK, "layout_page", payload)
597  			})
598  		} else {
599  			log.Printf("Warning: Post titled '%s' has an empty slug and will not be accessible via a unique URL.\n", localPost.Title)
600  		}
601  	}
602  
603  	for _, post := range postsData.Categories[0].Pages {
604  		localPost := post
605  		if localPost.Slug != "" {
606  			sidebarLinks := utils.CreateSidebarLinks(localPost.Headers)
607  			slug := strings.ReplaceAll(localPost.Slug, "\"", "")
608  			e.GET("/"+slug+"/", func(c echo.Context) error {
609  				var payload utils.Payload
610  				payload.BlogTitle = config.Blog.Title
611  				payload.BlogDesc = config.Blog.Desc
612  				payload.BlogCopyright = config.Blog.Copyright
613  				payload.Version = Version
614  				payload.FeedRss = feedrss
615  				payload.PinnedPosts = pinnedposts
616  				payload.Links = links
617  				payload.Title = strings.ReplaceAll(localPost.Title, "\"", "")
618  				payload.Date = strings.ReplaceAll(localPost.Date.Format(time.RFC1123), "\"", "")
619  				payload.Content = localPost.Content
620  				payload.ExternalData = externalData
621  				payload.PagesData = pagesData
622  				payload.PostsData = postsData
623  				payload.TextesData = textesData
624  				payload.Headers = localPost.Headers
625  				payload.Description = strings.ReplaceAll(localPost.Description, "\"", "")
626  				payload.Tags = localPost.Tags
627  				payload.SidebarLinks = sidebarLinks
628  				payload.CurrentSlug = strings.ReplaceAll(localPost.Slug, "\"", "")
629  				payload.MetaDescription = strings.ReplaceAll(localPost.MetaDescription, "\"", "")
630  				payload.MetaPropertyTitle = strings.ReplaceAll(localPost.MetaPropertyTitle, "\"", "")
631  				payload.MetaPropertyDescription = strings.ReplaceAll(localPost.MetaPropertyDescription, "\"", "")
632  				payload.MetaOgURL = strings.ReplaceAll(localPost.MetaOgURL, "\"", "")
633  
634  				return c.Render(http.StatusOK, "layout", payload)
635  			})
636  		} else {
637  			log.Printf("Warning: Post titled '%s' has an empty slug and will not be accessible via a unique URL.\n", localPost.Title)
638  		}
639  	}
640  
641  	for _, texte := range textesData.Categories[0].Pages {
642  		localPost := texte
643  		if localPost.Slug != "" {
644  			sidebarLinks := utils.CreateSidebarLinks(localPost.Headers)
645  			slug := strings.ReplaceAll(localPost.Slug, "\"", "")
646  			e.GET("/texte/"+slug+"/", func(c echo.Context) error {
647  				var payload utils.Payload
648  				payload.BlogTitle = config.Blog.Title
649  				payload.BlogDesc = config.Blog.Desc
650  				payload.BlogCopyright = config.Blog.Copyright
651  				payload.Version = Version
652  				payload.FeedRss = feedrss
653  				payload.PinnedPosts = pinnedposts
654  				payload.Links = links
655  				payload.Title = strings.ReplaceAll(localPost.Title, "\"", "")
656  				payload.Date = strings.ReplaceAll(localPost.Date.Format(time.RFC1123), "\"", "")
657  				payload.Content = localPost.Content
658  				payload.ExternalData = externalData
659  				payload.PagesData = pagesData
660  				payload.TextesData = textesData
661  				payload.Headers = localPost.Headers
662  				payload.Description = strings.ReplaceAll(localPost.Description, "\"", "")
663  				payload.SidebarLinks = sidebarLinks
664  				payload.CurrentSlug = strings.ReplaceAll(localPost.Slug, "\"", "")
665  				payload.MetaDescription = strings.ReplaceAll(localPost.MetaDescription, "\"", "")
666  				payload.MetaPropertyTitle = strings.ReplaceAll(localPost.MetaPropertyTitle, "\"", "")
667  				payload.MetaPropertyDescription = strings.ReplaceAll(localPost.MetaPropertyDescription, "\"", "")
668  				payload.MetaOgURL = strings.ReplaceAll(localPost.MetaOgURL, "\"", "")
669  
670  				return c.Render(http.StatusOK, "layout_texte", payload)
671  			})
672  		} else {
673  			log.Printf("Warning: Post titled '%s' has an empty slug and will not be accessible via a unique URL.\n", localPost.Title)
674  		}
675  	}
676  	return nil
677  }
678  
679  func main() {
680  	flag.StringVar(&ConfigFile, "configFile", "/usr/share/gibson/gibson.toml", "path to config file")
681  	showVersion := flag.Bool("version", false, "display the current version")
682  	flag.Parse()
683  
684  	if *showVersion {
685  		fmt.Println(Version)
686  		os.Exit(0)
687  	}
688  
689  	if _, err := toml.DecodeFile(ConfigFile, &config); err != nil {
690  		fmt.Println(err)
691  		return
692  	}
693  
694  	// Launch Http server for S3
695  	go httpServer()
696  
697  	e := echo.New()
698  	//e.Debug = true
699  	e.Use(middleware.Logger())
700  	e.Use(middleware.Recover())
701  	e.Use(ServerHeader)
702  
703  	e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
704  		//		ContentSecurityPolicy: "script-src 'unsafe-eval' 'unsafe-inline'",
705  		//		ContentSecurityPolicy: "script-src 'nonce-{RANDOM}' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https: http:;",
706  		XSSProtection:         "1; mode=block",
707  		ContentTypeNosniff:    "nosniff",
708  		XFrameOptions:         "SAMEORIGIN",
709  		HSTSMaxAge:            31536000,
710  		HSTSExcludeSubdomains: false,
711  		HSTSPreloadEnabled:    true,
712  	}))
713  
714  	feedrss = nil
715  	rssChannel = make(chan []utils.FeedRss)
716  	utils.GetRSS(rssChannel, config.RSS, &feedrss)
717  
718  	renderer := &Template{
719  		templates: template.Must(template.New("t").Funcs(template.FuncMap{
720  			"mkslice": mkslice,
721  			"mul0": func(i int, o int) int {
722  				return (i - 1) * o
723  			},
724  			"mul1": func(i int, o int) int {
725  				return i * o
726  			},
727  			"inc": func(i int) int {
728  				return i + 1
729  			},
730  			"sub": func(i int) int {
731  				o := i - 1
732  				if o <= 0 {
733  					o = 1
734  				}
735  				return o
736  			},
737  			"sup": func(i int) bool {
738  				if i > 4 {
739  					return true
740  				} else {
741  					return false
742  				}
743  			},
744  			"dict": utils.Dict,
745  		}).ParseGlob(config.Gibson.Directory + "templates/*.html")),
746  	}
747  
748  	e.Renderer = renderer
749  	e.Static("/static", config.Gibson.Directory+"static")
750  	e.Static("/images", config.Gibson.Directory+"static/images")
751  	e.Static("/.well-known", config.Gibson.Directory+"static/.well-known")
752  	e.HTTPErrorHandler = customHTTPErrorHandler
753  
754  	e.GET("/sse", handleSSE)
755  	e.GET("/reload", reloadMarkdown)
756  
757  	if len(config.S3.Endpoint) != 0 {
758  		mc := utils.NewMediaController(config.S3)
759  		e.GET("/s3/:id", mc.PlayFrontMediaFromHTTP)
760  	}
761  	e.GET("/index.xml", func(c echo.Context) error {
762  		return c.File("/tmp/index.xml")
763  	})
764  
765  	if err := loadMarkdown(e); err != nil {
766  		fmt.Println(err)
767  		return
768  	}
769  
770  	s := &http.Server{
771  		Addr:         config.Gibson.IP + ":" + strconv.Itoa(config.Gibson.Port),
772  		ReadTimeout:  60 * time.Minute,
773  		WriteTimeout: 60 * time.Minute,
774  	}
775  	e.Logger.Fatal(e.StartServer(s))
776  }