239 lines
5.3 KiB
Go
239 lines
5.3 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"database/sql"
|
|
"flag"
|
|
"html/template"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/Thomasorus/Ronbun-CMS/internal/models"
|
|
"github.com/alexedwards/scs/sqlite3store"
|
|
"github.com/alexedwards/scs/v2"
|
|
|
|
"github.com/go-playground/form/v4"
|
|
_ "github.com/ncruces/go-sqlite3/driver"
|
|
)
|
|
|
|
// Create a struct for dependencies injection
|
|
type application struct {
|
|
debug bool
|
|
devMode bool
|
|
logger *slog.Logger
|
|
pages *models.PageModel
|
|
users *models.UserModel
|
|
templateCache map[string]*template.Template
|
|
formDecoder *form.Decoder
|
|
sessionManager *scs.SessionManager
|
|
ogCache sync.Map
|
|
}
|
|
|
|
func main() {
|
|
// Parse command line arguments
|
|
addr := flag.String("addr", ":4000", "HTTP network address")
|
|
dsn := flag.String("dsn", "file:ronbun.db", "BDD data source name")
|
|
debug := flag.Bool("debug", false, "Enable debug mode")
|
|
devMode := flag.Bool("dev", false, "Enable dev mode")
|
|
|
|
// Create shared logger
|
|
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
|
Level: slog.LevelDebug,
|
|
AddSource: true,
|
|
}))
|
|
|
|
db, err := openDB(*dsn)
|
|
if err != nil {
|
|
logger.Error(err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Close the DB connection when main() is terminated
|
|
defer db.Close()
|
|
|
|
// Check if database has tables
|
|
tables, err := checkTables(db)
|
|
if err != nil {
|
|
logger.Error("failed to check tables", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
// If no tables that's the first launch, create the tables
|
|
if !tables {
|
|
if err := createTables(db); err != nil {
|
|
logger.Error("failed to create tables", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
err = os.MkdirAll("uploaded", 0755)
|
|
if err != nil {
|
|
logger.Error(err.Error())
|
|
}
|
|
|
|
err = os.MkdirAll("processed", 0755)
|
|
if err != nil {
|
|
logger.Error(err.Error())
|
|
}
|
|
|
|
filesIsEmpty, err := isFolderEmpty("./uploaded")
|
|
if err != nil {
|
|
logger.Error("failed to check uploaded folder", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
processedIsEmpty, err := isFolderEmpty("./processed")
|
|
if err != nil {
|
|
logger.Error("failed to check processed folder", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if !filesIsEmpty && processedIsEmpty {
|
|
imgProc := NewImageProcessor()
|
|
err = imgProc.ProcessBatch("uploaded")
|
|
if err != nil {
|
|
logger.Error(err.Error())
|
|
}
|
|
}
|
|
|
|
// Initialize Template cache
|
|
templateCache, err := newTemplateCache()
|
|
if err != nil {
|
|
logger.Error(err.Error())
|
|
os.Exit(1)
|
|
}
|
|
|
|
formDecoder := form.NewDecoder()
|
|
|
|
sessionManager := scs.New()
|
|
sessionManager.Store = sqlite3store.New(db)
|
|
sessionManager.Lifetime = 730 * time.Hour
|
|
sessionManager.Cookie.Secure = true
|
|
|
|
// Define application and its dependencies
|
|
app := &application{
|
|
debug: *debug,
|
|
devMode: *devMode,
|
|
logger: logger,
|
|
pages: &models.PageModel{DB: db},
|
|
users: &models.UserModel{DB: db},
|
|
templateCache: templateCache,
|
|
formDecoder: formDecoder,
|
|
sessionManager: sessionManager,
|
|
}
|
|
|
|
tlsConfig := &tls.Config{
|
|
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
|
|
}
|
|
|
|
// Serve the routes from app.routes
|
|
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", "addr", srv.Addr)
|
|
|
|
if !app.devMode {
|
|
err = srv.ListenAndServe()
|
|
} else {
|
|
err = srv.ListenAndServeTLS("./tls/cert.pem", "./tls/key.pem")
|
|
}
|
|
|
|
logger.Error(err.Error())
|
|
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Initializes the DB Pool, creates a connection and pings to see if db is working
|
|
func openDB(dsn string) (*sql.DB, error) {
|
|
// Does not create a connection but initializes the pool
|
|
db, err := sql.Open("sqlite3", dsn)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Creates a connection to ping and check if the DB is working
|
|
err = db.Ping()
|
|
if err != nil {
|
|
db.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return db, nil
|
|
}
|
|
|
|
func checkTables(db *sql.DB) (bool, error) {
|
|
stmt := "SELECT name FROM sqlite_master WHERE type='table' AND name='pages'"
|
|
|
|
var tableName string
|
|
err := db.QueryRow(stmt).Scan(&tableName)
|
|
|
|
if err == sql.ErrNoRows {
|
|
return false, nil
|
|
}
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func createTables(db *sql.DB) error {
|
|
stmt := `CREATE TABLE pages (
|
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
name VARCHAR(100) NOT NULL,
|
|
slug VARCHAR(100) NOT NULL,
|
|
host INTEGER NOT NULL DEFAULT 0,
|
|
category VARCHAR(100) NOT NULL,
|
|
summary TEXT NOT NULL,
|
|
body TEXT NOT NULL,
|
|
created DATETIME NOT NULL,
|
|
updated DATETIME NOT NULL,
|
|
private INT NOT NULL DEFAULT 0,
|
|
menu INT NOT NULL DEFAULT 0,
|
|
feed INT NOT NULL DEFAULT 0
|
|
);
|
|
|
|
CREATE TABLE sessions (
|
|
token TEXT PRIMARY KEY,
|
|
data BLOB NOT NULL,
|
|
expiry REAL NOT NULL
|
|
);
|
|
|
|
CREATE INDEX sessions_expiry_idx ON sessions(expiry);
|
|
|
|
|
|
CREATE TABLE users (
|
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
name VARCHAR(255) NOT NULL,
|
|
email VARCHAR(255) NOT NULL,
|
|
theme VARCHAR(255) NOT NULL,
|
|
hashed_password CHAR(60) NOT NULL,
|
|
created DATETIME NOT NULL
|
|
);
|
|
|
|
CREATE UNIQUE INDEX users_uc_email ON users (email);`
|
|
|
|
_, err := db.Exec(stmt)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
print("\nTables Created!\n\n")
|
|
return nil
|
|
}
|
|
|
|
func isFolderEmpty(path string) (bool, error) {
|
|
entries, err := os.ReadDir(path)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return len(entries) == 0, nil
|
|
}
|