Use config from .env and update api

This commit is contained in:
2026-01-24 18:09:24 +01:00
parent 1e02e659ab
commit 1c26770db0
11 changed files with 171 additions and 44 deletions

16
.env.example Normal file
View File

@@ -0,0 +1,16 @@
# Server Configuration
SERVER_PORT=8080
GIN_MODE=debug
# Security Configuration
JWT_SECRET=your-super-secret-jwt-key-here
# Sync Configuration
SYNC_INTERVAL_MINUTES=1
# Default Admin User Configuration
ADMIN_EMAIL=admin@admin.com
ADMIN_PASSWORD=sheetless
# Directory of your sheets
SHEETS_DIRECTORY=/data/sheets

1
.envrc
View File

@@ -1 +1,2 @@
use flake use flake
dotenv .env

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
sheetless-server sheetless-server
sheetless.db sheetless.db
/result /result
/.env

66
src/config/config.go Normal file
View File

@@ -0,0 +1,66 @@
package config
import (
"os"
"strconv"
"time"
)
type Config struct {
Server struct {
Port string
Mode string // gin.Mode: debug, release, test
}
JWT struct {
Secret string
}
Sync struct {
Interval time.Duration
}
Admin struct {
Email string
Password string
}
SheetsDirectory string
}
var AppConfig *Config
func Load() {
cfg := &Config{}
// Server configuration
cfg.Server.Port = getEnv("SERVER_PORT", ":8080")
cfg.Server.Mode = getEnv("GIN_MODE", "debug")
// JWT configuration
cfg.JWT.Secret = getEnv("JWT_SECRET", "sheetless-default-jwt-secret-please-change")
// Sync configuration
syncMinutes := getEnvInt("SYNC_INTERVAL_MINUTES", 1)
cfg.Sync.Interval = time.Duration(syncMinutes) * time.Minute
// Admin configuration
cfg.Admin.Email = getEnv("ADMIN_EMAIL", "admin@admin.com")
cfg.Admin.Password = getEnv("ADMIN_PASSWORD", "sheetless")
cfg.SheetsDirectory = getEnv("SHEETS_DIRECTORY", "./sheets_directory")
AppConfig = cfg
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getEnvInt(key string, defaultValue int) int {
if value := os.Getenv(key); value != "" {
if intValue, err := strconv.Atoi(value); err == nil {
return intValue
}
}
return defaultValue
}

View File

@@ -2,8 +2,10 @@ package database
import ( import (
"log" "log"
"sheetless-server/config"
"sheetless-server/models" "sheetless-server/models"
"golang.org/x/crypto/bcrypt"
"gorm.io/driver/sqlite" "gorm.io/driver/sqlite"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -12,16 +14,59 @@ var DB *gorm.DB
func InitDatabase() { func InitDatabase() {
var err error var err error
DB, err = gorm.Open(sqlite.Open("sheetless.db"), &gorm.Config{}) DB, err = gorm.Open(sqlite.Open("sheetless.db"), &gorm.Config{})
if err != nil { if err != nil {
log.Fatal("Failed to connect to database:", err) log.Fatal("Failed to connect to database:", err)
} }
tables, err := DB.Migrator().GetTables()
if err != nil {
log.Fatal("Failed to list tables of database:", err)
}
isNewDatabase := len(tables) == 0
// Auto migrate the schema // Auto migrate the schema
err = DB.AutoMigrate(&models.User{}, &models.Sheet{}, &models.Composer{}) err = DB.AutoMigrate(&models.User{}, &models.Sheet{}, &models.Composer{})
if err != nil { if err != nil {
log.Fatal("Failed to migrate database:", err) log.Fatal("Failed to migrate database:", err)
} }
if isNewDatabase {
createDefaultAdminUser()
}
log.Println("Database connected and migrated successfully") log.Println("Database connected and migrated successfully")
} }
func createDefaultAdminUser() {
// Check if admin user already exists
var existingUser models.User
err := DB.Where("email = ?", config.AppConfig.Admin.Email).First(&existingUser).Error
if err == nil {
// Admin user already exists, don't recreate
log.Printf("Admin user already exists: %s", config.AppConfig.Admin.Email)
return
}
// Hash the admin password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(config.AppConfig.Admin.Password), bcrypt.DefaultCost)
if err != nil {
log.Printf("Failed to hash admin password: %v", err)
return
}
// Create admin user
adminUser := models.User{
Username: "admin",
Email: config.AppConfig.Admin.Email,
Password: string(hashedPassword),
}
if err := DB.Create(&adminUser).Error; err != nil {
log.Printf("Failed to create admin user: %v", err)
return
}
log.Printf("Default admin user created with email: %s", config.AppConfig.Admin.Email)
}

View File

@@ -2,6 +2,7 @@ package handlers
import ( import (
"net/http" "net/http"
"sheetless-server/config"
"sheetless-server/database" "sheetless-server/database"
"sheetless-server/models" "sheetless-server/models"
"time" "time"
@@ -11,8 +12,6 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
var jwtSecret = []byte("your-secret-key") // TODO: In production, use environment variable
type RegisterRequest struct { type RegisterRequest struct {
Username string `json:"username" binding:"required"` Username string `json:"username" binding:"required"`
Email string `json:"email" binding:"required,email"` Email string `json:"email" binding:"required,email"`
@@ -86,7 +85,7 @@ func Login(c *gin.Context) {
"exp": time.Now().Add(time.Hour * 24).Unix(), "exp": time.Now().Add(time.Hour * 24).Unix(),
}) })
tokenString, err := token.SignedString(jwtSecret) tokenString, err := token.SignedString([]byte(config.AppConfig.JWT.Secret))
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"}) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
return return

View File

@@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"sheetless-server/config"
"sheetless-server/database" "sheetless-server/database"
"sheetless-server/models" "sheetless-server/models"
"sheetless-server/utils" "sheetless-server/utils"
@@ -16,15 +17,6 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
) )
const uploadDir = "./uploads"
func init() {
// Create uploads directory if it doesn't exist
if err := os.MkdirAll(uploadDir, 0755); err != nil {
panic("Failed to create uploads directory: " + err.Error())
}
}
func UploadSheet(c *gin.Context) { func UploadSheet(c *gin.Context) {
// Get form data // Get form data
title := c.PostForm("title") title := c.PostForm("title")
@@ -69,7 +61,7 @@ func UploadSheet(c *gin.Context) {
// Generate unique filename // Generate unique filename
filename := fmt.Sprintf("%d%s", time.Now().Unix(), filepath.Ext(header.Filename)) filename := fmt.Sprintf("%d%s", time.Now().Unix(), filepath.Ext(header.Filename))
filePath := filepath.Join(uploadDir, filename) filePath := filepath.Join(config.AppConfig.SheetsDirectory, filename)
// Save file // Save file
out, err := os.Create(filePath) out, err := os.Create(filePath)
@@ -106,15 +98,15 @@ func UploadSheet(c *gin.Context) {
// Create database record // Create database record
sheet := models.Sheet{ sheet := models.Sheet{
Uuid: *uuid, Uuid: *uuid,
Title: title, Title: title,
Description: description, Description: description,
FilePath: filePath, FilePath: filePath,
FileSize: fileInfo.Size(), FileSize: fileInfo.Size(),
FileHash: fileHash, FileHash: fileHash,
ComposerId: composer.Uuid, ComposerUuid: composer.Uuid,
CreatedAt: time.Now(), CreatedAt: time.Now(),
UpdatedAt: time.Now(), UpdatedAt: time.Now(),
} }
if err := database.DB.Create(&sheet).Error; err != nil { if err := database.DB.Create(&sheet).Error; err != nil {

View File

@@ -2,6 +2,8 @@ package main
import ( import (
"log" "log"
"os"
"sheetless-server/config"
"sheetless-server/database" "sheetless-server/database"
"sheetless-server/routes" "sheetless-server/routes"
"sheetless-server/sync" "sheetless-server/sync"
@@ -11,11 +13,18 @@ import (
) )
func main() { func main() {
config.Load()
gin.SetMode(config.AppConfig.Server.Mode)
database.InitDatabase() database.InitDatabase()
if err := os.MkdirAll(config.AppConfig.SheetsDirectory, 0755); err != nil {
panic("Failed to create uploads directory: " + err.Error())
}
// Start sync runner // Start sync runner
go func() { go func() {
ticker := time.NewTicker(1 * time.Minute) ticker := time.NewTicker(config.AppConfig.Sync.Interval)
defer ticker.Stop() defer ticker.Stop()
for { for {
select { select {
@@ -31,8 +40,8 @@ func main() {
routes.SetupRoutes(r) routes.SetupRoutes(r)
log.Println("Server starting on port 8080...") log.Printf("Server starting on port %s...", config.AppConfig.Server.Port)
if err := r.Run(":8080"); err != nil { if err := r.Run(config.AppConfig.Server.Port); err != nil {
log.Fatal("Failed to start server:", err) log.Fatal("Failed to start server:", err)
} }
} }

View File

@@ -2,14 +2,13 @@ package middleware
import ( import (
"net/http" "net/http"
"sheetless-server/config"
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
) )
var jwtSecret = []byte("your-secret-key") // Should match the one in handlers/auth.go
func AuthMiddleware() gin.HandlerFunc { func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization") authHeader := c.GetHeader("Authorization")
@@ -29,7 +28,7 @@ func AuthMiddleware() gin.HandlerFunc {
// Parse and validate token // Parse and validate token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil return []byte(config.AppConfig.JWT.Secret), nil
}) })
if err != nil || !token.Valid { if err != nil || !token.Valid {

View File

@@ -8,15 +8,15 @@ import (
) )
type Sheet struct { type Sheet struct {
Uuid uuid.UUID `json:"uuid" gorm:"type:uuid;primaryKey"` Uuid uuid.UUID `json:"uuid" gorm:"type:uuid;primaryKey"`
Title string `json:"title" gorm:"not null"` Title string `json:"title" gorm:"not null"`
Description string `json:"description"` Description string `json:"description"`
FilePath string `json:"file_path" gorm:"not null"` FilePath string `json:"file_path" gorm:"not null"`
FileSize int64 `json:"file_size"` FileSize int64 `json:"file_size"`
FileHash uint64 `json:"file_hash"` FileHash uint64 `json:"file_hash"`
ComposerId uuid.UUID `json:"composer_id"` ComposerUuid uuid.UUID `json:"composer_uuid"`
Composer Composer `json:"composer" gorm:"foreignKey:ComposerId"` Composer Composer `json:"composer" gorm:"foreignKey:ComposerUuid"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
} }

View File

@@ -4,6 +4,7 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"sheetless-server/config"
"sheetless-server/database" "sheetless-server/database"
"sheetless-server/handlers" "sheetless-server/handlers"
"sheetless-server/models" "sheetless-server/models"
@@ -11,8 +12,6 @@ import (
"time" "time"
) )
const uploadDir = "./uploads"
func SyncSheets() error { func SyncSheets() error {
// Get all sheets // Get all sheets
var sheets []models.Sheet var sheets []models.Sheet
@@ -30,7 +29,7 @@ func SyncSheets() error {
} }
// Walk uploads dir // Walk uploads dir
files, err := os.ReadDir(uploadDir) files, err := os.ReadDir(config.AppConfig.SheetsDirectory)
if err != nil { if err != nil {
return err return err
} }
@@ -39,7 +38,7 @@ func SyncSheets() error {
if file.IsDir() { if file.IsDir() {
continue continue
} }
filePath := filepath.Join(uploadDir, file.Name()) filePath := filepath.Join(config.AppConfig.SheetsDirectory, file.Name())
hash, err := utils.FileHash(filePath) hash, err := utils.FileHash(filePath)
if err != nil { if err != nil {
log.Printf("Error hashing file %s: %v", filePath, err) log.Printf("Error hashing file %s: %v", filePath, err)