151 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			151 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package main
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/tls"
 | 
						|
	"database/sql"
 | 
						|
	"flag"
 | 
						|
	"log/slog"
 | 
						|
	"net/http"
 | 
						|
	"os"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
	"unicode"
 | 
						|
 | 
						|
	"git.32bit.cafe/32bitcafe/guestbook/internal/models"
 | 
						|
	"github.com/alexedwards/scs/sqlite3store"
 | 
						|
	"github.com/alexedwards/scs/v2"
 | 
						|
	"github.com/gorilla/schema"
 | 
						|
	_ "github.com/mattn/go-sqlite3"
 | 
						|
)
 | 
						|
 | 
						|
type application struct {
 | 
						|
	sequence          uint16
 | 
						|
	logger            *slog.Logger
 | 
						|
	websites          *models.WebsiteModel
 | 
						|
	guestbooks        *models.GuestbookModel
 | 
						|
	users             *models.UserModel
 | 
						|
	guestbookComments *models.GuestbookCommentModel
 | 
						|
	sessionManager    *scs.SessionManager
 | 
						|
	formDecoder       *schema.Decoder
 | 
						|
	debug             bool
 | 
						|
	timezones         []string
 | 
						|
}
 | 
						|
 | 
						|
func main() {
 | 
						|
	addr := flag.String("addr", ":3000", "HTTP network address")
 | 
						|
	dsn := flag.String("dsn", "guestbook.db", "data source name")
 | 
						|
	debug := flag.Bool("debug", false, "enable debug mode")
 | 
						|
	flag.Parse()
 | 
						|
 | 
						|
	logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
 | 
						|
 | 
						|
	db, err := openDB(*dsn)
 | 
						|
	if err != nil {
 | 
						|
		logger.Error(err.Error())
 | 
						|
		os.Exit(1)
 | 
						|
	}
 | 
						|
	defer db.Close()
 | 
						|
 | 
						|
	sessionManager := scs.New()
 | 
						|
	sessionManager.Store = sqlite3store.New(db)
 | 
						|
	sessionManager.Lifetime = 12 * time.Hour
 | 
						|
 | 
						|
	formDecoder := schema.NewDecoder()
 | 
						|
	formDecoder.IgnoreUnknownKeys(true)
 | 
						|
 | 
						|
	app := &application{
 | 
						|
		sequence:          0,
 | 
						|
		logger:            logger,
 | 
						|
		sessionManager:    sessionManager,
 | 
						|
		websites:          &models.WebsiteModel{DB: db},
 | 
						|
		guestbooks:        &models.GuestbookModel{DB: db},
 | 
						|
		users:             &models.UserModel{DB: db},
 | 
						|
		guestbookComments: &models.GuestbookCommentModel{DB: db},
 | 
						|
		formDecoder:       formDecoder,
 | 
						|
		debug:             *debug,
 | 
						|
		timezones:         getAvailableTimezones(),
 | 
						|
	}
 | 
						|
 | 
						|
	tlsConfig := &tls.Config{
 | 
						|
		CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
 | 
						|
	}
 | 
						|
 | 
						|
	srv := &http.Server{
 | 
						|
		Addr:         *addr,
 | 
						|
		Handler:      app.routes(),
 | 
						|
		ErrorLog:     slog.NewLogLogger(logger.Handler(), slog.LevelError),
 | 
						|
		TLSConfig:    tlsConfig,
 | 
						|
		IdleTimeout:  time.Minute,
 | 
						|
		ReadTimeout:  5 * time.Second,
 | 
						|
		WriteTimeout: 10 * time.Second,
 | 
						|
	}
 | 
						|
 | 
						|
	logger.Info("Starting server", slog.Any("addr", *addr))
 | 
						|
 | 
						|
	if app.debug {
 | 
						|
		err = srv.ListenAndServeTLS("./tls/cert.pem", "./tls/key.pem")
 | 
						|
	} else {
 | 
						|
		err = srv.ListenAndServe()
 | 
						|
	}
 | 
						|
	logger.Error(err.Error())
 | 
						|
	os.Exit(1)
 | 
						|
}
 | 
						|
 | 
						|
func openDB(dsn string) (*sql.DB, error) {
 | 
						|
	db, err := sql.Open("sqlite3", dsn)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if err = db.Ping(); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return db, nil
 | 
						|
}
 | 
						|
 | 
						|
func getAvailableTimezones() []string {
 | 
						|
	var zones []string
 | 
						|
	var zoneDirs = []string{
 | 
						|
		"/usr/share/zoneinfo/",
 | 
						|
		"/usr/share/lib/zoneinfo/",
 | 
						|
		"/usr/lib/locale/TZ/",
 | 
						|
	}
 | 
						|
	for _, zd := range zoneDirs {
 | 
						|
		zones = walkTzDir(zd, zones)
 | 
						|
		for idx, zone := range zones {
 | 
						|
			zones[idx] = strings.ReplaceAll(zone, zd+"/", "")
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return zones
 | 
						|
}
 | 
						|
 | 
						|
func walkTzDir(path string, zones []string) []string {
 | 
						|
	fileInfos, err := os.ReadDir(path)
 | 
						|
	if err != nil {
 | 
						|
		return zones
 | 
						|
	}
 | 
						|
	isAlpha := func(s string) bool {
 | 
						|
		for _, r := range s {
 | 
						|
			if !unicode.IsLetter(r) {
 | 
						|
				return false
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	for _, info := range fileInfos {
 | 
						|
		if info.Name() != strings.ToUpper(info.Name()[:1])+info.Name()[1:] {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if !isAlpha(info.Name()[:1]) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		newPath := path + "/" + info.Name()
 | 
						|
		if info.IsDir() {
 | 
						|
			zones = walkTzDir(newPath, zones)
 | 
						|
		} else {
 | 
						|
			zones = append(zones, newPath)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return zones
 | 
						|
 | 
						|
}
 |