193 lines
4.9 KiB
Go
193 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"ResendIt/internal/api/middleware"
|
|
"ResendIt/internal/auth"
|
|
"ResendIt/internal/config"
|
|
"ResendIt/internal/db"
|
|
"ResendIt/internal/file"
|
|
"ResendIt/internal/logger"
|
|
"ResendIt/internal/user"
|
|
"ResendIt/internal/util"
|
|
"ResendIt/internal/web"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"html/template"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/joho/godotenv"
|
|
)
|
|
|
|
func main() {
|
|
err := godotenv.Load()
|
|
if err != nil {
|
|
fmt.Printf("Error loading .env file\n")
|
|
}
|
|
|
|
os.Setenv("LOG_FORMAT", "json")
|
|
|
|
logger.Log.Info().Str("type", "startup").Msg("Starting ReSendIt")
|
|
|
|
dbCon, err := db.Connect()
|
|
if err != nil {
|
|
logger.Log.Fatal().Err(err).Str("type", "startup").Msg("Failed to connect to database")
|
|
}
|
|
|
|
err = dbCon.AutoMigrate(&user.User{}, &file.FileRecord{}, &config.ConfigEntry{})
|
|
if err != nil {
|
|
logger.Log.Error().Err(err).Str("type", "startup").Msg("Database migration failed")
|
|
return
|
|
}
|
|
|
|
// create temp folder
|
|
path := "./tmp"
|
|
if os.IsExist(os.Mkdir(path, os.ModePerm)) {
|
|
logger.Log.Info().Str("type", "startup").Msg("Temp folder already exists")
|
|
} else {
|
|
if err := os.MkdirAll(path, os.ModePerm); err != nil {
|
|
logger.Log.Error().Err(err).Str("type", "startup").Msg("Failed to create temp folder")
|
|
return
|
|
}
|
|
}
|
|
|
|
// Use gin.New() instead of gin.Default() to have custom middleware
|
|
r := gin.New()
|
|
// Add structured logging and recovery middleware
|
|
r.Use(middleware.StructuredLogger())
|
|
r.Use(gin.Recovery())
|
|
|
|
r.MaxMultipartMemory = 10 << 30
|
|
r.SetFuncMap(template.FuncMap{
|
|
"add": func(a, b int) int { return a + b },
|
|
"sub": func(a, b int) int { return a - b },
|
|
"humanSize": util.HumanSize,
|
|
})
|
|
|
|
r.LoadHTMLGlob("templates/*.html")
|
|
r.Static("/static", "./static")
|
|
|
|
// Add request ID middleware
|
|
r.Use(middleware.RequestIDMiddleware())
|
|
|
|
r.GET("/ping", func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "hello",
|
|
})
|
|
})
|
|
|
|
r.NoRoute(func(c *gin.Context) {
|
|
c.HTML(404, "error.html", nil)
|
|
})
|
|
|
|
authRepo := auth.NewRepository(dbCon)
|
|
authService := auth.NewService(authRepo)
|
|
authHandler := auth.NewHandler(authService)
|
|
|
|
userRepo := user.NewRepository(dbCon)
|
|
userService := user.NewService(userRepo)
|
|
userHandler := user.NewHandler(userService)
|
|
|
|
configRepo := config.NewRepository(dbCon)
|
|
configService := config.NewService(configRepo)
|
|
|
|
if err := configService.EnsureDefaults(); err != nil {
|
|
logger.Log.Fatal().Err(err).Str("type", "startup").Msg("Failed to ensure config defaults")
|
|
}
|
|
|
|
fileRepo := file.NewRepository(dbCon)
|
|
fileService := file.NewService(fileRepo, "./uploads")
|
|
fileHandler := file.NewHandler(fileService, configService)
|
|
|
|
createAdminUser(userService)
|
|
|
|
apiRoute := r.Group("/api")
|
|
// General API rate limiting to reduce abuse/spam.
|
|
apiRoute.Use(middleware.RateLimitByIPDynamic(
|
|
func() int {
|
|
return configService.GetIntDefault(config.KeyRateLimitApiPerMinute, config.DefaultRateLimitApiPerMinute)
|
|
},
|
|
time.Minute,
|
|
func() int {
|
|
return configService.GetIntDefault(config.KeyRateLimitApiBurst, config.DefaultRateLimitApiBurst)
|
|
},
|
|
5*time.Minute,
|
|
))
|
|
|
|
auth.RegisterRoutes(apiRoute, authHandler, configService)
|
|
user.RegisterRoutes(apiRoute, userHandler)
|
|
file.RegisterRoutes(apiRoute, fileHandler)
|
|
|
|
webHandler := web.NewHandler(fileService, configService)
|
|
web.RegisterRoutes(r, webHandler, userService)
|
|
|
|
port := os.Getenv("PORT")
|
|
if port == "" {
|
|
port = "8080"
|
|
}
|
|
|
|
domain := os.Getenv("DOMAIN")
|
|
if domain == "" {
|
|
domain = "http://localhost:" + port + "/"
|
|
os.Setenv("DOMAIN", domain)
|
|
}
|
|
|
|
logger.Log.Info().
|
|
Str("type", "startup").
|
|
Str("port", port).
|
|
Str("domain", domain).
|
|
Msg("Server starting")
|
|
|
|
err = r.Run(":" + port)
|
|
if err != nil {
|
|
logger.Log.Error().Err(err).Str("type", "startup").Msg("Server failed to start")
|
|
return
|
|
}
|
|
}
|
|
|
|
func generateRandomPassword(length int) string {
|
|
b := make([]byte, length)
|
|
if _, err := rand.Read(b); err != nil {
|
|
panic(err)
|
|
}
|
|
return base64.URLEncoding.EncodeToString(b)[:length]
|
|
}
|
|
|
|
func createAdminUser(service *user.Service) {
|
|
_, err := service.FindByUsername("admin")
|
|
|
|
if err == nil {
|
|
logger.Log.Info().Str("type", "startup").Msg("Admin user already exists")
|
|
return
|
|
} else if errors.Is(err, user.ErrUserNotFound) {
|
|
logger.Log.Info().Str("type", "startup").Msg("Creating admin user")
|
|
|
|
password := generateRandomPassword(16)
|
|
adminUser, err := service.CreateUser("admin", password, "admin")
|
|
if err != nil {
|
|
logger.Log.Error().Err(err).Str("type", "startup").Msg("Failed to create admin user")
|
|
return
|
|
}
|
|
|
|
adminUser.ForceChangePassword = true
|
|
_, err = service.UpdateUser(adminUser)
|
|
|
|
if err != nil {
|
|
logger.Log.Error().Err(err).Str("type", "startup").Msg("Failed to update admin user")
|
|
} else {
|
|
logger.Log.Info().
|
|
Str("type", "startup").
|
|
Str("password", password).
|
|
Msg("Admin user created")
|
|
}
|
|
return
|
|
}
|
|
|
|
logger.Log.Error().Err(err).Str("type", "startup").Msg("Error checking for admin user")
|
|
return
|
|
}
|