add current db layout and data
add handling for mounted export template directory fixed naming problems and port exposure
This commit is contained in:
@@ -56,6 +56,14 @@ func main() {
|
|||||||
database.ConnectDatabase()
|
database.ConnectDatabase()
|
||||||
logger.Info("Datenbankverbindung erfolgreich")
|
logger.Info("Datenbankverbindung erfolgreich")
|
||||||
|
|
||||||
|
// Initialize PDF templates
|
||||||
|
logger.Debug("Initialisiere PDF-Templates...")
|
||||||
|
if err := pdfrender.InitializeTemplates("/app/default_templates", cfg.TemplatesDir); err != nil {
|
||||||
|
logger.Warn("Fehler beim Initialisieren der Templates: %s", err.Error())
|
||||||
|
} else {
|
||||||
|
logger.Info("PDF-Templates erfolgreich initialisiert")
|
||||||
|
}
|
||||||
|
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
router.SetupGin(r)
|
router.SetupGin(r)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package pdfrender
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bamort/logger"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitializeTemplates copies default templates to target directory if they don't exist
|
||||||
|
// or if the content has changed (for development updates)
|
||||||
|
// This should be called once during application startup
|
||||||
|
func InitializeTemplates(defaultDir, targetDir string) error {
|
||||||
|
// Check if default directory exists
|
||||||
|
if _, err := os.Stat(defaultDir); os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("default templates directory does not exist: %s", defaultDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read default templates
|
||||||
|
entries, err := os.ReadDir(defaultDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read default templates: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each template directory
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
srcDir := filepath.Join(defaultDir, entry.Name())
|
||||||
|
dstDir := filepath.Join(targetDir, entry.Name())
|
||||||
|
|
||||||
|
// Copy or update template directory
|
||||||
|
updated, err := syncDir(srcDir, dstDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to sync template %s: %w", entry.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updated {
|
||||||
|
logger.Info("Initialized/updated template: %s", entry.Name())
|
||||||
|
} else {
|
||||||
|
logger.Debug("Template %s is up to date", entry.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncDir synchronizes source directory to destination, copying only changed files
|
||||||
|
// Returns true if any files were updated
|
||||||
|
func syncDir(src, dst string) (bool, error) {
|
||||||
|
// Get source directory info
|
||||||
|
srcInfo, err := os.Stat(src)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create destination directory if it doesn't exist
|
||||||
|
if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read source directory entries
|
||||||
|
entries, err := os.ReadDir(src)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updated := false
|
||||||
|
// Copy each entry
|
||||||
|
for _, entry := range entries {
|
||||||
|
srcPath := filepath.Join(src, entry.Name())
|
||||||
|
dstPath := filepath.Join(dst, entry.Name())
|
||||||
|
|
||||||
|
if entry.IsDir() {
|
||||||
|
// Recursively sync subdirectory
|
||||||
|
subUpdated, err := syncDir(srcPath, dstPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if subUpdated {
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check if file needs to be copied
|
||||||
|
needsCopy, err := fileNeedsUpdate(srcPath, dstPath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if needsCopy {
|
||||||
|
if err := copyFile(srcPath, dstPath); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileNeedsUpdate checks if destination file is missing or differs from source
|
||||||
|
func fileNeedsUpdate(src, dst string) (bool, error) {
|
||||||
|
// If destination doesn't exist, needs update
|
||||||
|
dstInfo, err := os.Stat(dst)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get source info
|
||||||
|
srcInfo, err := os.Stat(src)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quick check: if sizes differ, files differ
|
||||||
|
if srcInfo.Size() != dstInfo.Size() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare file contents
|
||||||
|
return filesContentDiffer(src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
// filesContentDiffer compares file contents
|
||||||
|
func filesContentDiffer(file1, file2 string) (bool, error) {
|
||||||
|
content1, err := os.ReadFile(file1)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
content2, err := os.ReadFile(file2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files differ if contents don't match
|
||||||
|
return string(content1) != string(content2), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyFile copies a single file
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
// Open source file
|
||||||
|
srcFile, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcFile.Close()
|
||||||
|
|
||||||
|
// Get source file info
|
||||||
|
srcInfo, err := srcFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create destination file
|
||||||
|
dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, srcInfo.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dstFile.Close()
|
||||||
|
|
||||||
|
// Copy contents
|
||||||
|
if _, err := io.Copy(dstFile, srcFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package pdfrender
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInitializeTemplates(t *testing.T) {
|
||||||
|
// Setup test directories
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
defaultDir := filepath.Join(tmpDir, "default_templates")
|
||||||
|
targetDir := filepath.Join(tmpDir, "templates")
|
||||||
|
|
||||||
|
// Create default templates directory with test files
|
||||||
|
if err := os.MkdirAll(filepath.Join(defaultDir, "TestTemplate"), 0755); err != nil {
|
||||||
|
t.Fatalf("Failed to create default template dir: %v", err)
|
||||||
|
}
|
||||||
|
testFile := filepath.Join(defaultDir, "TestTemplate", "page1.html")
|
||||||
|
if err := os.WriteFile(testFile, []byte("test content"), 0644); err != nil {
|
||||||
|
t.Fatalf("Failed to create test file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 1: Copy when target directory doesn't exist
|
||||||
|
if err := InitializeTemplates(defaultDir, targetDir); err != nil {
|
||||||
|
t.Errorf("InitializeTemplates failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify file was copied
|
||||||
|
copiedFile := filepath.Join(targetDir, "TestTemplate", "page1.html")
|
||||||
|
if _, err := os.Stat(copiedFile); os.IsNotExist(err) {
|
||||||
|
t.Error("Expected file was not copied")
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := os.ReadFile(copiedFile)
|
||||||
|
if err != nil || string(content) != "test content" {
|
||||||
|
t.Error("Copied file content doesn't match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Don't overwrite when content is identical
|
||||||
|
if err := InitializeTemplates(defaultDir, targetDir); err != nil {
|
||||||
|
t.Errorf("InitializeTemplates failed on second run: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify file still has same content
|
||||||
|
content, err = os.ReadFile(copiedFile)
|
||||||
|
if err != nil || string(content) != "test content" {
|
||||||
|
t.Error("File content should remain unchanged")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Update when default template changed
|
||||||
|
updatedContent := []byte("updated test content")
|
||||||
|
if err := os.WriteFile(testFile, updatedContent, 0644); err != nil {
|
||||||
|
t.Fatalf("Failed to update source file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := InitializeTemplates(defaultDir, targetDir); err != nil {
|
||||||
|
t.Errorf("InitializeTemplates failed after source update: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify file was updated
|
||||||
|
content, err = os.ReadFile(copiedFile)
|
||||||
|
if err != nil || string(content) != "updated test content" {
|
||||||
|
t.Error("File should have been updated with new content")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: Handle missing default directory gracefully
|
||||||
|
if err := InitializeTemplates("/nonexistent", targetDir); err == nil {
|
||||||
|
t.Error("Expected error for nonexistent default directory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitializeTemplatesWithMultipleFiles(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
defaultDir := filepath.Join(tmpDir, "default_templates")
|
||||||
|
targetDir := filepath.Join(tmpDir, "templates")
|
||||||
|
|
||||||
|
// Create multiple templates and files
|
||||||
|
templates := []string{"Template1", "Template2"}
|
||||||
|
for _, tmpl := range templates {
|
||||||
|
tmplDir := filepath.Join(defaultDir, tmpl)
|
||||||
|
if err := os.MkdirAll(tmplDir, 0755); err != nil {
|
||||||
|
t.Fatalf("Failed to create template dir: %v", err)
|
||||||
|
}
|
||||||
|
for i := 1; i <= 3; i++ {
|
||||||
|
file := filepath.Join(tmplDir, filepath.Base(tmpl)+".html")
|
||||||
|
if err := os.WriteFile(file, []byte("content "+tmpl), 0644); err != nil {
|
||||||
|
t.Fatalf("Failed to create file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize templates
|
||||||
|
if err := InitializeTemplates(defaultDir, targetDir); err != nil {
|
||||||
|
t.Fatalf("InitializeTemplates failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all templates were copied
|
||||||
|
for _, tmpl := range templates {
|
||||||
|
tmplDir := filepath.Join(targetDir, tmpl)
|
||||||
|
if _, err := os.Stat(tmplDir); os.IsNotExist(err) {
|
||||||
|
t.Errorf("Template directory %s was not copied", tmpl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
bamort-db*
|
bamort-db*
|
||||||
|
templates
|
||||||
@@ -37,6 +37,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
# Copy the compiled binary from builder stage
|
# Copy the compiled binary from builder stage
|
||||||
COPY --from=builder /app/server /app
|
COPY --from=builder /app/server /app
|
||||||
|
COPY --from=builder /app/templates /app/default_templates
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 8180
|
EXPOSE 8180
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
version: "3.8"
|
|
||||||
services:
|
services:
|
||||||
backend-dev:
|
backend-dev:
|
||||||
build:
|
build:
|
||||||
@@ -11,9 +10,9 @@ services:
|
|||||||
- GO_ENV=development
|
- GO_ENV=development
|
||||||
- CGO_ENABLED=1
|
- CGO_ENABLED=1
|
||||||
- DATABASE_TYPE=mysql
|
- DATABASE_TYPE=mysql
|
||||||
- DATABASE_URL=bamort:bG4)efozrc@tcp(mariadb:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local
|
- DATABASE_URL=bamort:bG4)efozrc@tcp(mariadb-dev:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local
|
||||||
depends_on:
|
depends_on:
|
||||||
mariadb:
|
mariadb-dev:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
working_dir: /app
|
working_dir: /app
|
||||||
# Restart if Go code changes cause crash
|
# Restart if Go code changes cause crash
|
||||||
@@ -39,7 +38,7 @@ services:
|
|||||||
- ../frontend:/app
|
- ../frontend:/app
|
||||||
- /app/node_modules # Prevent overwriting node_modules
|
- /app/node_modules # Prevent overwriting node_modules
|
||||||
|
|
||||||
mariadb:
|
mariadb-dev:
|
||||||
image: mariadb:11.4
|
image: mariadb:11.4
|
||||||
container_name: bamort-mariadb-dev
|
container_name: bamort-mariadb-dev
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -61,21 +60,21 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
phpmyadmin:
|
phpmyadmin-dev:
|
||||||
image: phpmyadmin/phpmyadmin:5.2
|
image: phpmyadmin/phpmyadmin:5.2
|
||||||
container_name: bamort-phpmyadmin-dev
|
container_name: bamort-phpmyadmin-dev
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "8081:80"
|
- "8081:80"
|
||||||
environment:
|
environment:
|
||||||
PMA_HOST: mariadb
|
PMA_HOST: mariadb-dev
|
||||||
PMA_PORT: 3306
|
PMA_PORT: 3306
|
||||||
PMA_USER: root
|
PMA_USER: root
|
||||||
PMA_PASSWORD: root_password_dev
|
PMA_PASSWORD: root_password_dev
|
||||||
MYSQL_ROOT_PASSWORD: root_password_dev
|
MYSQL_ROOT_PASSWORD: root_password_dev
|
||||||
PMA_ARBITRARY: 1
|
PMA_ARBITRARY: 1
|
||||||
depends_on:
|
depends_on:
|
||||||
mariadb:
|
mariadb-dev:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
version: "3.8"
|
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
build:
|
build:
|
||||||
@@ -16,19 +15,17 @@ services:
|
|||||||
mariadb:
|
mariadb:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
working_dir: /app
|
working_dir: /app
|
||||||
|
volumes:
|
||||||
|
- ./templates:/app/templates
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: ../frontend
|
context: ../frontend
|
||||||
dockerfile: ../docker/Dockerfile.frontend
|
dockerfile: ../docker/Dockerfile.frontend
|
||||||
container_name: bamort-frontend
|
container_name: bamort-frontend
|
||||||
ports:
|
ports:
|
||||||
- "5173:80"
|
- "80:80"
|
||||||
environment:
|
environment:
|
||||||
- ENVIRONMENT=production
|
- ENVIRONMENT=production
|
||||||
- VITE_API_URL=${VITE_API_URL:-http://bamort.trokan.de:8180}
|
- VITE_API_URL=${VITE_API_URL:-http://bamort.trokan.de:8180}
|
||||||
@@ -56,8 +53,8 @@ services:
|
|||||||
- ./init-db:/docker-entrypoint-initdb.d
|
- ./init-db:/docker-entrypoint-initdb.d
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||||
start_period: 30s
|
start_period: 20s
|
||||||
timeout: 15s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
# phpMyAdmin - Database Management (commented out for production)
|
# phpMyAdmin - Database Management (commented out for production)
|
||||||
|
|||||||
Reference in New Issue
Block a user