aboutsummaryrefslogblamecommitdiff
path: root/app/fcuny-net/main.go
blob: 9a8a4f2f6468f6ec66364d2be06719ff7a8a21a3 (plain) (tree)























































































































































































































































                                                                                                                                                            
                                                                            

          
package main

import (
	"context"
	"embed"
	"fmt"
	"html/template"
	"io/fs"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path"
	"runtime"
	"syscall"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"rsc.io/markdown"
)

//go:embed content/*
var contentFiles embed.FS

//go:embed static/*
var staticFiles embed.FS

//go:embed templates/*
var templateFiles embed.FS

const (
	domain      = "fcuny.net"
	forgeDomain = "code.fcuny.net"
	forgeUser   = "fcuny"
)

type PageData struct {
	Title   string
	Content template.HTML
}

var (
	goGetReqs = promauto.NewCounterVec(prometheus.CounterOpts{
		Name: "goget_requests_total",
		Help: "go get requests processed, by repository name.",
	}, []string{"name"})

	staticReqs = promauto.NewCounterVec(prometheus.CounterOpts{
		Name: "static_requests_total",
		Help: "HTTP requests served from the FS.",
	}, []string{"path"})

	modules = []string{"x"}
)

func main() {
	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer cancel()

	metricsMux := http.NewServeMux()
	metricsMux.Handle("/metrics", promhttp.Handler())
	metricsMux.Handle("/.info", info())

	metricsServer := &http.Server{
		Addr:        ":9091",
		Handler:     metricsMux,
		ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second,
	}

	go func() {
		log.Println("Starting metrics server on :9091")
		if err := metricsServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Printf("Metrics server error: %v", err)
		}
	}()

	s := &http.Server{
		Addr:         ":8070",
		Handler:      handler(),
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
		IdleTimeout:  1 * time.Minute,
	}

	go func() {
		log.Println("Starting main server on :8070")
		if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Printf("Main server error: %v", err)
		}
	}()

	<-ctx.Done()
	log.Println("Shutdown signal received, starting graceful shutdown...")

	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer shutdownCancel()

	var shutdownErr error

	log.Println("Shutting down main server...")
	if err := s.Shutdown(shutdownCtx); err != nil {
		log.Printf("Main server shutdown error: %v", err)
		shutdownErr = err
	}

	log.Println("Shutting down metrics server...")
	if err := metricsServer.Shutdown(shutdownCtx); err != nil {
		log.Printf("Metrics server shutdown error: %v", err)
		if shutdownErr == nil {
			shutdownErr = err
		}
	}

	if shutdownErr != nil {
		log.Printf("Shutdown completed with errors")
		os.Exit(1)
	}

	log.Println("Shutdown completed successfully")
}

func handler() http.Handler {
	mux := http.NewServeMux()

	staticFS, _ := fs.Sub(staticFiles, "static")
	fileServer := http.FileServer(http.FS(staticFS))
	mux.Handle("/static/", http.StripPrefix("/static/", fileServer))

	mux.HandleFunc("/", homeHandler)
	mux.HandleFunc("/resume", resumeHandler)

	for _, name := range modules {
		module := "/" + name
		mux.Handle(
			module+"/",
			goImportHandler(module, "https://"+forgeDomain+"/"+forgeUser+"/"+name),
		)
	}

	return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet && r.Method != http.MethodHead {
			status := http.StatusMethodNotAllowed
			http.Error(rw, http.StatusText(status), status)
			return
		}
		mux.ServeHTTP(rw, r)
	})
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	markdownContent, err := contentFiles.ReadFile("content/home.md")
	if err != nil {
		log.Printf("Error reading home markdown: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	parser := markdown.Parser{}
	doc := parser.Parse(string(markdownContent))

	htmlContent := markdown.ToHTML(doc)

	tmplContent, err := templateFiles.ReadFile("templates/home.html")
	if err != nil {
		log.Printf("Error reading template: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	tmpl, err := template.New("layout").Parse(string(tmplContent))
	if err != nil {
		log.Printf("Error parsing template: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	data := PageData{
		Title:   "Franck Cuny",
		Content: template.HTML(htmlContent),
	}

	w.Header().Set("Content-Type", "text/html; charset=UTF-8")
	if err := tmpl.Execute(w, data); err != nil {
		log.Printf("Error executing template: %v", err)
	}
	staticReqs.WithLabelValues(path.Clean(r.URL.Path)).Inc()
}

func resumeHandler(w http.ResponseWriter, r *http.Request) {
	markdownContent, err := contentFiles.ReadFile("content/resume.md")
	if err != nil {
		log.Printf("Error reading resume markdown: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	parser := markdown.Parser{}
	doc := parser.Parse(string(markdownContent))

	htmlContent := markdown.ToHTML(doc)

	tmplContent, err := templateFiles.ReadFile("templates/resume.html")
	if err != nil {
		log.Printf("Error reading template: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	tmpl, err := template.New("layout").Parse(string(tmplContent))
	if err != nil {
		log.Printf("Error parsing template: %v", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		return
	}

	data := PageData{
		Title:   "Work Experience",
		Content: template.HTML(htmlContent),
	}

	w.Header().Set("Content-Type", "text/html; charset=UTF-8")
	if err := tmpl.Execute(w, data); err != nil {
		log.Printf("Error executing template: %v", err)
	}
	staticReqs.WithLabelValues(path.Clean(r.URL.Path)).Inc()
}

func goImportHandler(module, repo string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		goGetReqs.WithLabelValues(module).Inc()

		if r.URL.Query().Get("go-get") == "1" {
			w.Header().Set("Content-Type", "text/html; charset=UTF-8")
			importPath := domain + module
			if _, err := fmt.Fprintf(w, `<html><head><meta name="go-import" content="%s git %s"></head></html>`, importPath, repo); err != nil {
				log.Printf("Error writing response: %v", err)
			}
			return
		}

		http.Redirect(w, r, repo, http.StatusFound)
	})
}

func info() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_, _ = fmt.Fprintf(w, "Go version: %s\n", runtime.Version())
	})
}