first commit
This commit is contained in:
241
internal/router/router.go
Normal file
241
internal/router/router.go
Normal file
@@ -0,0 +1,241 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"homework-manager/internal/config"
|
||||
"homework-manager/internal/handler"
|
||||
"homework-manager/internal/middleware"
|
||||
"homework-manager/internal/service"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/cookie"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func getFuncMap() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"formatDate": func(t time.Time) string {
|
||||
return t.Format("2006/01/02")
|
||||
},
|
||||
"formatDateTime": func(t time.Time) string {
|
||||
return t.Format("2006/01/02 15:04")
|
||||
},
|
||||
"formatDateInput": func(t time.Time) string {
|
||||
return t.Format("2006-01-02T15:04")
|
||||
},
|
||||
"isOverdue": func(t time.Time, completed bool) bool {
|
||||
return !completed && time.Now().After(t)
|
||||
},
|
||||
"daysUntil": func(t time.Time) int {
|
||||
return int(time.Until(t).Hours() / 24)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func loadTemplates() (*template.Template, error) {
|
||||
tmpl := template.New("").Funcs(getFuncMap())
|
||||
|
||||
baseContent, err := os.ReadFile("web/templates/layouts/base.html")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
templateDirs := []struct {
|
||||
pattern string
|
||||
prefix string
|
||||
}{
|
||||
{"web/templates/auth/*.html", ""},
|
||||
{"web/templates/pages/*.html", ""},
|
||||
{"web/templates/assignments/*.html", "assignments/"},
|
||||
{"web/templates/admin/*.html", "admin/"},
|
||||
}
|
||||
|
||||
for _, dir := range templateDirs {
|
||||
files, err := filepath.Glob(dir.pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range files {
|
||||
name := dir.prefix + filepath.Base(file)
|
||||
content, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reDefine := regexp.MustCompile(`{{\s*define\s+"([^"]+)"\s*}}`)
|
||||
reTemplate := regexp.MustCompile(`{{\s*template\s+"([^"]+)"\s*([^}]*)\s*}}`)
|
||||
|
||||
uniqueBase := reDefine.ReplaceAllStringFunc(string(baseContent), func(m string) string {
|
||||
match := reDefine.FindStringSubmatch(m)
|
||||
blockName := match[1]
|
||||
if blockName == "head" || blockName == "scripts" || blockName == "content" || blockName == "base" {
|
||||
return strings.Replace(m, blockName, name+"_"+blockName, 1)
|
||||
}
|
||||
return m
|
||||
})
|
||||
|
||||
uniqueBase = reTemplate.ReplaceAllStringFunc(uniqueBase, func(m string) string {
|
||||
match := reTemplate.FindStringSubmatch(m)
|
||||
blockName := match[1]
|
||||
if blockName == "head" || blockName == "scripts" || blockName == "content" || blockName == "base" {
|
||||
return strings.Replace(m, blockName, name+"_"+blockName, 1)
|
||||
}
|
||||
return m
|
||||
})
|
||||
uniqueContent := reDefine.ReplaceAllStringFunc(string(content), func(m string) string {
|
||||
match := reDefine.FindStringSubmatch(m)
|
||||
blockName := match[1]
|
||||
if blockName == "head" || blockName == "scripts" || blockName == "content" {
|
||||
return strings.Replace(m, blockName, name+"_"+blockName, 1)
|
||||
}
|
||||
return m
|
||||
})
|
||||
|
||||
uniqueContent = reTemplate.ReplaceAllStringFunc(uniqueContent, func(m string) string {
|
||||
match := reTemplate.FindStringSubmatch(m)
|
||||
blockName := match[1]
|
||||
if blockName == "base" {
|
||||
return strings.Replace(m, blockName, name+"_"+blockName, 1)
|
||||
}
|
||||
return m
|
||||
})
|
||||
|
||||
combined := uniqueBase + "\n" + uniqueContent
|
||||
_, err = tmpl.New(name).Parse(combined)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tmpl, nil
|
||||
}
|
||||
|
||||
func Setup(cfg *config.Config) *gin.Engine {
|
||||
if !cfg.Debug {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
r := gin.Default()
|
||||
|
||||
if len(cfg.TrustedProxies) > 0 {
|
||||
r.SetTrustedProxies(cfg.TrustedProxies)
|
||||
}
|
||||
tmpl, err := loadTemplates()
|
||||
if err != nil {
|
||||
panic("Failed to load templates: " + err.Error())
|
||||
}
|
||||
r.SetHTMLTemplate(tmpl)
|
||||
|
||||
r.Static("/static", "web/static")
|
||||
|
||||
store := cookie.NewStore([]byte(cfg.SessionSecret))
|
||||
store.Options(sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: 86400 * 7, // 7 days
|
||||
HttpOnly: true,
|
||||
Secure: cfg.HTTPS,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
r.Use(sessions.Sessions("session", store))
|
||||
r.Use(middleware.RequestTimer())
|
||||
|
||||
securityConfig := middleware.SecurityConfig{
|
||||
HTTPS: cfg.HTTPS,
|
||||
}
|
||||
r.Use(middleware.SecurityHeaders(securityConfig))
|
||||
r.Use(middleware.ForceHTTPS(securityConfig))
|
||||
|
||||
r.Use(middleware.RateLimit(middleware.RateLimitConfig{
|
||||
Enabled: cfg.RateLimitEnabled,
|
||||
Requests: cfg.RateLimitRequests,
|
||||
Window: cfg.RateLimitWindow,
|
||||
}))
|
||||
|
||||
csrfMiddleware := middleware.CSRF(middleware.CSRFConfig{
|
||||
Secret: cfg.CSRFSecret,
|
||||
})
|
||||
|
||||
authService := service.NewAuthService()
|
||||
apiKeyService := service.NewAPIKeyService()
|
||||
|
||||
authHandler := handler.NewAuthHandler()
|
||||
assignmentHandler := handler.NewAssignmentHandler()
|
||||
adminHandler := handler.NewAdminHandler()
|
||||
profileHandler := handler.NewProfileHandler()
|
||||
apiHandler := handler.NewAPIHandler()
|
||||
|
||||
guest := r.Group("/")
|
||||
guest.Use(middleware.GuestOnly())
|
||||
guest.Use(csrfMiddleware)
|
||||
{
|
||||
guest.GET("/login", authHandler.ShowLogin)
|
||||
guest.POST("/login", authHandler.Login)
|
||||
if cfg.AllowRegistration {
|
||||
guest.GET("/register", authHandler.ShowRegister)
|
||||
guest.POST("/register", authHandler.Register)
|
||||
} else {
|
||||
guest.GET("/register", func(c *gin.Context) {
|
||||
c.HTML(http.StatusForbidden, "error.html", gin.H{
|
||||
"title": "登録無効",
|
||||
"message": "新規登録は現在受け付けておりません。",
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
auth := r.Group("/")
|
||||
auth.Use(middleware.AuthRequired(authService))
|
||||
auth.Use(csrfMiddleware)
|
||||
{
|
||||
auth.GET("/", assignmentHandler.Dashboard)
|
||||
auth.POST("/logout", authHandler.Logout)
|
||||
|
||||
auth.GET("/assignments", assignmentHandler.Index)
|
||||
auth.GET("/assignments/new", assignmentHandler.New)
|
||||
auth.POST("/assignments", assignmentHandler.Create)
|
||||
auth.GET("/assignments/:id/edit", assignmentHandler.Edit)
|
||||
auth.POST("/assignments/:id", assignmentHandler.Update)
|
||||
auth.POST("/assignments/:id/toggle", assignmentHandler.Toggle)
|
||||
auth.POST("/assignments/:id/delete", assignmentHandler.Delete)
|
||||
|
||||
auth.GET("/profile", profileHandler.Show)
|
||||
auth.POST("/profile", profileHandler.Update)
|
||||
auth.POST("/profile/password", profileHandler.ChangePassword)
|
||||
admin := auth.Group("/admin")
|
||||
admin.Use(middleware.AdminRequired())
|
||||
{
|
||||
admin.GET("/users", adminHandler.Index)
|
||||
admin.POST("/users/:id/delete", adminHandler.DeleteUser)
|
||||
admin.POST("/users/:id/role", adminHandler.ChangeRole)
|
||||
|
||||
admin.GET("/api-keys", adminHandler.APIKeys)
|
||||
admin.POST("/api-keys", adminHandler.CreateAPIKey)
|
||||
admin.POST("/api-keys/:id/delete", adminHandler.DeleteAPIKey)
|
||||
}
|
||||
}
|
||||
|
||||
api := r.Group("/api/v1")
|
||||
api.Use(middleware.APIKeyAuth(apiKeyService))
|
||||
{
|
||||
api.GET("/assignments", apiHandler.ListAssignments)
|
||||
api.GET("/assignments/pending", apiHandler.ListPendingAssignments)
|
||||
api.GET("/assignments/completed", apiHandler.ListCompletedAssignments)
|
||||
api.GET("/assignments/overdue", apiHandler.ListOverdueAssignments)
|
||||
api.GET("/assignments/due-today", apiHandler.ListDueTodayAssignments)
|
||||
api.GET("/assignments/due-this-week", apiHandler.ListDueThisWeekAssignments)
|
||||
api.GET("/assignments/:id", apiHandler.GetAssignment)
|
||||
api.POST("/assignments", apiHandler.CreateAssignment)
|
||||
api.PUT("/assignments/:id", apiHandler.UpdateAssignment)
|
||||
api.DELETE("/assignments/:id", apiHandler.DeleteAssignment)
|
||||
api.PATCH("/assignments/:id/toggle", apiHandler.ToggleAssignment)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
Reference in New Issue
Block a user