Animations - added opacity animation for experience page - added stagger animation for navbar items on mobile - added animation for landing page for years and language indications Pages - About Page mockup Redesign - Navbar to include icons on desktop
112 lines
2.9 KiB
Go
112 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
_ "embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/a-h/templ"
|
|
"github.com/moh682/portfolio/components"
|
|
"github.com/moh682/portfolio/domain"
|
|
"github.com/moh682/portfolio/middlewares"
|
|
)
|
|
|
|
func templHandlerWithOptions(component templ.Component) http.Handler {
|
|
return templ.Handler(component, templ.WithStatus(200), templ.WithContentType("text/html"))
|
|
}
|
|
|
|
func rootHandler(handler http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
handler.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// SafeFileServer wraps http.FileServer to only serve specific file types from specific directories
|
|
func SafeFileServer(root http.FileSystem) http.Handler {
|
|
fileServer := http.FileServer(root)
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Set security headers
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
w.Header().Set("X-Frame-Options", "DENY")
|
|
w.Header().Set("X-XSS-Protection", "1; mode=block")
|
|
|
|
log.Println("assets handler", r.URL.Path)
|
|
|
|
// Only allow specific file extensions
|
|
if allowed := func(path string) bool {
|
|
allowedExts := map[string]bool{
|
|
".js": true,
|
|
".css": true,
|
|
".png": true,
|
|
".webp": true,
|
|
".jpg": true,
|
|
".jpeg": true,
|
|
".svg": true,
|
|
".ico": true,
|
|
}
|
|
for ext := range allowedExts {
|
|
if strings.HasSuffix(path, ext) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}(r.URL.Path); !allowed {
|
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
fileServer.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
//go:embed assets/json/experiences.json
|
|
var embeddedExperiences []byte
|
|
|
|
//go:embed assets/json/projects.json
|
|
var embeddedProjects []byte
|
|
|
|
func main() {
|
|
server := http.NewServeMux()
|
|
|
|
// Serve static files with restrictions
|
|
staticFS := http.Dir("assets")
|
|
server.Handle("GET /assets/", http.StripPrefix("/assets/", SafeFileServer(staticFS)))
|
|
|
|
server.Handle("GET /about", templHandlerWithOptions(components.AboutPage()))
|
|
|
|
var exps []domain.Experience
|
|
err := json.Unmarshal(embeddedExperiences, &exps)
|
|
if err != nil {
|
|
log.Panic(err)
|
|
}
|
|
server.Handle("GET /experiences", templHandlerWithOptions(components.ExperiencesPage(exps)))
|
|
|
|
var projects []domain.Project
|
|
err = json.Unmarshal(embeddedProjects, &projects)
|
|
if err != nil {
|
|
log.Panic(err)
|
|
}
|
|
server.Handle("GET /projects", templHandlerWithOptions(components.ProjectsPage(projects)))
|
|
|
|
server.Handle("GET /contact", templHandlerWithOptions(components.ContactPage()))
|
|
server.Handle("GET /{$}", templHandlerWithOptions(components.LandingPage()))
|
|
|
|
// 404 fallback page
|
|
server.Handle("GET /", templ.Handler(components.NotFound(), templ.WithStatus(404), templ.WithContentType("text/html")))
|
|
|
|
// TODO: add Blogs to my portfolio website
|
|
// server.Handle("GET /blogs", templHandlerWithOptions(components.AboutPage()))
|
|
|
|
fmt.Println("Listening on :8080")
|
|
log.Panic(
|
|
http.ListenAndServe(":8080",
|
|
middlewares.Combine(
|
|
middlewares.Logger(server),
|
|
),
|
|
),
|
|
)
|
|
}
|