218 lines
5.6 KiB
Go
218 lines
5.6 KiB
Go
//go:build !prod
|
|
// +build !prod
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"crud/sqlite"
|
|
"embed"
|
|
"fmt"
|
|
"html/template"
|
|
"io/fs"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
exitCodeErr = 1
|
|
exitCodeInterrupt = 2
|
|
)
|
|
|
|
//go:embed frontend
|
|
var frontend embed.FS
|
|
|
|
//go:embed templates
|
|
var templates embed.FS
|
|
|
|
var templatesCompiled = template.Must(template.ParseFS(templates, "templates/*.html"))
|
|
|
|
// main is the entry point of the application.
|
|
// Its task is to check wether all execution conditions are fullfilled.
|
|
// Collecting information from the environment: flags, environment vars, configs.
|
|
// Calling the run() function.
|
|
func main() {
|
|
|
|
fmt.Println("Developement mode")
|
|
|
|
ctx := context.Background()
|
|
|
|
// logging
|
|
logFileName := "./crud.log"
|
|
logFile, err := os.OpenFile(logFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
|
if err != nil {
|
|
log.Printf("error opening file: %v", err)
|
|
os.Exit(exitCodeErr)
|
|
}
|
|
defer logFile.Close()
|
|
log.SetOutput(logFile)
|
|
|
|
// database
|
|
dbName := "./user.db"
|
|
db := sqlite.New(dbName)
|
|
if err != nil {
|
|
log.Printf("Failed to open %s database: %v", dbName, err)
|
|
os.Exit(exitCodeErr)
|
|
}
|
|
|
|
// run the app
|
|
if err := run(db, ctx); err != nil {
|
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
|
os.Exit(exitCodeErr)
|
|
}
|
|
|
|
}
|
|
|
|
// Setting up all dependencies
|
|
// Creating the server (a central http handler)
|
|
func run(db *sqlite.Database, ctx context.Context) error {
|
|
|
|
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
|
|
defer cancel()
|
|
|
|
err := db.Open(ctx)
|
|
if err != nil {
|
|
log.Printf("Failed to open %s database: %v", db.Name(), err)
|
|
os.Exit(exitCodeErr)
|
|
}
|
|
defer db.Close()
|
|
|
|
server := NewServer(ctx, db)
|
|
|
|
httpServer := &http.Server{
|
|
Addr: ":8080",
|
|
Handler: server,
|
|
}
|
|
|
|
go func() {
|
|
log.Printf("listening on %s\n", httpServer.Addr)
|
|
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
fmt.Fprintf(os.Stderr, "error listening and serving: %s\n", err)
|
|
}
|
|
}()
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
<-ctx.Done()
|
|
// make a new context for the Shutdown (thanks Alessandro Rosetti)
|
|
// shutdownCtx := context.Background()
|
|
shutdownCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
if err := httpServer.Shutdown(shutdownCtx); err != nil {
|
|
fmt.Fprintf(os.Stderr, "error shutting down http server: %s\n", err)
|
|
}
|
|
log.Printf("shut down http server on %s\n", httpServer.Addr)
|
|
}()
|
|
wg.Wait()
|
|
return nil
|
|
}
|
|
|
|
// The NewServer constructor is responsible for all the top-level HTTP stuff that applies to all endpoints, like CORS, auth middleware, and logging:
|
|
func NewServer(ctx context.Context, db *sqlite.Database) http.Handler {
|
|
mux := http.NewServeMux()
|
|
|
|
static, err := fs.Sub(frontend, "frontend")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Print the embedded filesystem tree
|
|
fmt.Println("Embedded frontend filesystem tree:")
|
|
err = printFSTree(static, ".", 0)
|
|
if err != nil {
|
|
log.Printf("Error printing filesystem tree: %v\n", err)
|
|
}
|
|
fmt.Println("--- End of tree ---")
|
|
|
|
addRoutes(
|
|
ctx,
|
|
mux,
|
|
db,
|
|
static,
|
|
templatesCompiled,
|
|
)
|
|
|
|
var handler http.Handler = mux
|
|
|
|
// handler = authMiddleware(handler)
|
|
handler = headerMiddleware(handler)
|
|
|
|
return handler
|
|
}
|
|
|
|
// printFSTree prints a tree-like structure of the given filesystem.
|
|
func printFSTree(efs fs.FS, root string, indentLevel int) error {
|
|
return fs.WalkDir(efs, root, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Skip the root directory itself for cleaner output if it's "."
|
|
if path == "." && root == "." {
|
|
return nil
|
|
}
|
|
|
|
indent := strings.Repeat("│ ", indentLevel)
|
|
connector := "├── "
|
|
// For the last item in a directory, use a different connector.
|
|
// This requires knowing if it's the last item, which fs.WalkDir doesn't directly provide.
|
|
// For simplicity, we'll use the same connector for all items.
|
|
// A more sophisticated approach would involve reading directory entries first.
|
|
|
|
fmt.Printf("%s%s%s\n", indent, connector, filepath.Base(path))
|
|
|
|
if d.IsDir() && path != root { // Avoid infinite recursion for the root itself if not handled carefully
|
|
// The WalkDir function handles recursion, so we don't need to call printFSTree recursively here.
|
|
// We adjust indentLevel based on path depth for visual representation.
|
|
// This simple indentation based on WalkDir's path might not be perfect for deep structures
|
|
// but gives a good overview.
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// authMiddleware is a simple authentication middleware
|
|
// func authMiddleware(next http.Handler) http.Handler {
|
|
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// user, pass, ok := r.BasicAuth()
|
|
// if !ok || !validateUser(user, pass) {
|
|
// w.Header().Set("WWW-Authenticate", `Basic realm="Please enter your credentials"`)
|
|
// http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
// return
|
|
// }
|
|
// next.ServeHTTP(w, r)
|
|
// })
|
|
// }
|
|
|
|
// authMiddleware is a simple authentication middleware
|
|
func headerMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Println("Request URL:", r.URL.String())
|
|
fmt.Println("Request Headers:")
|
|
for key, values := range r.Header {
|
|
for _, value := range values {
|
|
if key == "Referer" || strings.HasPrefix(key, "Hx") {
|
|
|
|
fmt.Printf("%s: %s\n", key, value)
|
|
}
|
|
}
|
|
}
|
|
fmt.Println()
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// validateUser validates the user credentials
|
|
func validateUser(username, password string) bool {
|
|
// In a real application, these credentials should be stored securely.
|
|
return strings.EqualFold(username, "admin") && password == "password"
|
|
}
|