package template import ( "fmt" "log/slog" "os" "path/filepath" "time" ) // VersionManager manages template versioning and rollback type VersionManager struct { basePath string versions map[string][]VersionInfo logger *slog.Logger } // VersionInfo holds information about a template version type VersionInfo struct { Version string `json:"version"` Timestamp time.Time `json:"timestamp"` FilePath string `json:"file_path"` Hash string `json:"hash"` Description string `json:"description"` Author string `json:"author"` Size int64 `json:"size"` Active bool `json:"active"` } // VersionHistory holds the complete version history for a template type VersionHistory struct { TemplateName string `json:"template_name"` Versions []VersionInfo `json:"versions"` Current VersionInfo `json:"current"` } // NewVersionManager creates a new version manager func NewVersionManager(basePath string) *VersionManager { return &VersionManager{ basePath: basePath, versions: make(map[string][]VersionInfo), logger: slog.With("component", "version_manager"), } } // SaveVersion saves a new version of a template func (vm *VersionManager) SaveVersion(templateName, templatePath, description, author string) error { // Create versions directory versionsDir := filepath.Join(vm.basePath, "versions", templateName) if err := os.MkdirAll(versionsDir, 0755); err != nil { return fmt.Errorf("failed to create versions directory: %w", err) } // Generate version info version := vm.generateVersion() versionFile := filepath.Join(versionsDir, fmt.Sprintf("%s_%s.html", templateName, version)) // Read current template content content, err := os.ReadFile(templatePath) if err != nil { return fmt.Errorf("failed to read template: %w", err) } // Calculate hash hash := vm.calculateHash(content) // Get file info info, err := os.Stat(templatePath) if err != nil { return fmt.Errorf("failed to get file info: %w", err) } // Create version info versionInfo := VersionInfo{ Version: version, Timestamp: time.Now(), FilePath: versionFile, Hash: hash, Description: description, Author: author, Size: info.Size(), Active: true, } // Save template content if err := os.WriteFile(versionFile, content, 0644); err != nil { return fmt.Errorf("failed to save version: %w", err) } // Update versions map if vm.versions[templateName] == nil { vm.versions[templateName] = []VersionInfo{} } // Deactivate previous versions for i := range vm.versions[templateName] { vm.versions[templateName][i].Active = false } vm.versions[templateName] = append(vm.versions[templateName], versionInfo) // Persist version metadata if err := vm.saveVersionMetadata(templateName); err != nil { vm.logger.Warn("failed to save version metadata", "error", err) } vm.logger.Info("template version saved", "template", templateName, "version", version, "author", author) return nil } // Rollback rolls back to a specific version func (vm *VersionManager) Rollback(templateName, version string) error { versions := vm.versions[templateName] if len(versions) == 0 { return fmt.Errorf("no versions found for template: %s", templateName) } var targetVersion *VersionInfo for i := range versions { if versions[i].Version == version { targetVersion = &versions[i] break } } if targetVersion == nil { return fmt.Errorf("version %s not found for template %s", version, templateName) } // Read version content content, err := os.ReadFile(targetVersion.FilePath) if err != nil { return fmt.Errorf("failed to read version content: %w", err) } // Write to current template currentPath := filepath.Join(vm.basePath, templateName+".html") if err := os.WriteFile(currentPath, content, 0644); err != nil { return fmt.Errorf("failed to restore version: %w", err) } // Update active state for i := range versions { versions[i].Active = versions[i].Version == version } vm.logger.Info("template rolled back", "template", templateName, "version", version) return nil } // GetVersionHistory returns the version history for a template func (vm *VersionManager) GetVersionHistory(templateName string) (*VersionHistory, error) { versions := vm.versions[templateName] if len(versions) == 0 { return nil, fmt.Errorf("no versions found for template: %s", templateName) } var current VersionInfo for _, v := range versions { if v.Active { current = v break } } return &VersionHistory{ TemplateName: templateName, Versions: versions, Current: current, }, nil } // ListTemplates returns all templates with versions func (vm *VersionManager) ListTemplates() map[string][]VersionInfo { return vm.versions } // DeleteVersion deletes a specific version func (vm *VersionManager) DeleteVersion(templateName, version string) error { versions := vm.versions[templateName] if len(versions) == 0 { return fmt.Errorf("no versions found for template: %s", templateName) } var newVersions []VersionInfo for _, v := range versions { if v.Version != version { newVersions = append(newVersions, v) } else { // Remove file if err := os.Remove(v.FilePath); err != nil && !os.IsNotExist(err) { vm.logger.Warn("failed to delete version file", "file", v.FilePath, "error", err) } } } vm.versions[templateName] = newVersions vm.logger.Info("template version deleted", "template", templateName, "version", version) return nil } // CleanupOldVersions removes old versions beyond retention limit func (vm *VersionManager) CleanupOldVersions(templateName string, retentionCount int) error { versions := vm.versions[templateName] if len(versions) <= retentionCount { return nil } // Sort by timestamp (newest first) sorted := make([]VersionInfo, len(versions)) copy(sorted, versions) // Keep only the latest versions vm.versions[templateName] = sorted[:retentionCount] // Delete old files for _, v := range sorted[retentionCount:] { if err := os.Remove(v.FilePath); err != nil && !os.IsNotExist(err) { vm.logger.Warn("failed to delete old version file", "file", v.FilePath, "error", err) } } vm.logger.Info("old versions cleaned up", "template", templateName, "kept", retentionCount, "deleted", len(sorted)-retentionCount) return nil } // generateVersion generates a unique version identifier func (vm *VersionManager) generateVersion() string { return time.Now().Format("20060102_150405") } // calculateHash calculates a simple hash for content func (vm *VersionManager) calculateHash(content []byte) string { // Simple hash - in production, use a proper hash function return fmt.Sprintf("%x", len(content)) } // saveVersionMetadata saves version metadata to disk func (vm *VersionManager) saveVersionMetadata(templateName string) error { // This could be implemented to persist metadata to a JSON file // For now, it's kept in memory return nil } // LoadVersionMetadata loads version metadata from disk func (vm *VersionManager) LoadVersionMetadata() error { // This could be implemented to load metadata from a JSON file // For now, it's kept in memory return nil } // GetTemplateStats returns statistics for a template func (vm *VersionManager) GetTemplateStats(templateName string) map[string]interface{} { versions := vm.versions[templateName] stats := map[string]interface{}{ "total_versions": len(versions), "current_version": "", "oldest_version": "", "newest_version": "", "total_size": int64(0), } if len(versions) > 0 { stats["current_version"] = versions[len(versions)-1].Version stats["oldest_version"] = versions[0].Version stats["newest_version"] = versions[len(versions)-1].Version var totalSize int64 for _, v := range versions { totalSize += v.Size } stats["total_size"] = totalSize } return stats } // ExportVersion exports a specific version to a file func (vm *VersionManager) ExportVersion(templateName, version, exportPath string) error { versions := vm.versions[templateName] var versionInfo *VersionInfo for _, v := range versions { if v.Version == version { versionInfo = &v break } } if versionInfo == nil { return fmt.Errorf("version %s not found for template %s", version, templateName) } content, err := os.ReadFile(versionInfo.FilePath) if err != nil { return fmt.Errorf("failed to read version content: %w", err) } if err := os.WriteFile(exportPath, content, 0644); err != nil { return fmt.Errorf("failed to export version: %w", err) } vm.logger.Info("template version exported", "template", templateName, "version", version, "export_path", exportPath) return nil } // ImportVersion imports a template from a file func (vm *VersionManager) ImportVersion(templateName, importPath, description, author string) error { if _, err := os.Stat(importPath); os.IsNotExist(err) { return fmt.Errorf("import file does not exist: %s", importPath) } return vm.SaveVersion(templateName, importPath, description, author) } // GetStoragePath returns the storage path for versions func (vm *VersionManager) GetStoragePath(templateName string) string { return filepath.Join(vm.basePath, "versions", templateName) } // ValidateVersion validates a version before saving func (vm *VersionManager) ValidateVersion(templatePath string) error { if _, err := os.Stat(templatePath); os.IsNotExist(err) { return fmt.Errorf("template file does not exist: %s", templatePath) } // Add more validation as needed return nil } // GetAllTemplatesWithVersions returns all templates and their versions func (vm *VersionManager) GetAllTemplatesWithVersions() map[string]interface{} { result := make(map[string]interface{}) for templateName := range vm.versions { stats := vm.GetTemplateStats(templateName) history, _ := vm.GetVersionHistory(templateName) result[templateName] = map[string]interface{}{ "stats": stats, "history": history, } } return result } // BackupAllTemplates creates a backup of all templates func (vm *VersionManager) BackupAllTemplates(backupPath string) error { if err := os.MkdirAll(backupPath, 0755); err != nil { return fmt.Errorf("failed to create backup directory: %w", err) } for templateName := range vm.versions { versions := vm.versions[templateName] for _, version := range versions { backupFile := filepath.Join(backupPath, fmt.Sprintf("%s_%s.html", templateName, version.Version)) if err := vm.ExportVersion(templateName, version.Version, backupFile); err != nil { vm.logger.Warn("failed to backup version", "template", templateName, "version", version.Version, "error", err) } } } vm.logger.Info("all templates backed up", "backup_path", backupPath) return nil } // RestoreFromBackup restores templates from a backup func (vm *VersionManager) RestoreFromBackup(backupPath string) error { return filepath.Walk(backupPath, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && filepath.Ext(path) == ".html" { filename := info.Name() // Parse template name and version from filename // This is a simplified implementation - ignore filename _ = filename return nil } return nil }) } // GetDiskUsage returns disk usage statistics func (vm *VersionManager) GetDiskUsage() (int64, error) { var totalSize int64 versionsDir := filepath.Join(vm.basePath, "versions") if _, err := os.Stat(versionsDir); os.IsNotExist(err) { return 0, nil } err := filepath.Walk(versionsDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() { totalSize += info.Size() } return nil }) return totalSize, err } // CleanupStorage removes empty directories and orphaned files func (vm *VersionManager) CleanupStorage() error { versionsDir := filepath.Join(vm.basePath, "versions") return filepath.Walk(versionsDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { // Remove empty directories files, err := os.ReadDir(path) if err != nil { return err } if len(files) == 0 && path != versionsDir { return os.Remove(path) } } return nil }) } // GetHealth returns the health status of the version manager func (vm *VersionManager) GetHealth() map[string]interface{} { diskUsage, _ := vm.GetDiskUsage() return map[string]interface{}{ "status": "healthy", "disk_usage": diskUsage, "total_templates": len(vm.versions), "storage_path": vm.GetStoragePath(""), } } // ValidateTemplateContent validates template content func (vm *VersionManager) ValidateTemplateContent(content []byte) error { // Basic validation - check for empty content if len(content) == 0 { return fmt.Errorf("template content is empty") } // Add more validation as needed return nil } // GetVersionDiff returns differences between two versions func (vm *VersionManager) GetVersionDiff(templateName, version1, version2 string) (string, error) { // This could be implemented to show differences between versions // For now, return a placeholder return "Version diff not implemented", nil } // SetRetentionPolicy sets the retention policy for versions func (vm *VersionManager) SetRetentionPolicy(templateName string, maxVersions int) error { return vm.CleanupOldVersions(templateName, maxVersions) } // GetRetentionPolicy returns the retention policy for a template func (vm *VersionManager) GetRetentionPolicy(templateName string) int { // Default retention policy return 10 } // PurgeAllVersions removes all versions for a template func (vm *VersionManager) PurgeAllVersions(templateName string) error { storagePath := vm.GetStoragePath(templateName) if err := os.RemoveAll(storagePath); err != nil { return fmt.Errorf("failed to purge versions: %w", err) } delete(vm.versions, templateName) vm.logger.Info("all versions purged", "template", templateName) return nil } // CloneTemplate creates a copy of a template with a new name func (vm *VersionManager) CloneTemplate(sourceTemplate, newTemplate, description, author string) error { versions := vm.versions[sourceTemplate] if len(versions) == 0 { return fmt.Errorf("source template not found: %s", sourceTemplate) } // Get the latest version latestVersion := versions[len(versions)-1] // Read the latest version content content, err := os.ReadFile(latestVersion.FilePath) if err != nil { return fmt.Errorf("failed to read source template: %w", err) } // Save as new template newTemplatePath := filepath.Join(vm.basePath, newTemplate+".html") if err := os.WriteFile(newTemplatePath, content, 0644); err != nil { return fmt.Errorf("failed to create new template: %w", err) } return vm.SaveVersion(newTemplate, newTemplatePath, description, author) } // GetTemplateUsage returns usage statistics for templates func (vm *VersionManager) GetTemplateUsage() map[string]interface{} { usage := make(map[string]interface{}) for templateName := range vm.versions { stats := vm.GetTemplateStats(templateName) usage[templateName] = stats } return usage } // OptimizeStorage optimizes storage by compressing old versions func (vm *VersionManager) OptimizeStorage() error { // This could be implemented to compress old versions // For now, just log the operation vm.logger.Info("storage optimization started") return nil } // GetTemplateInfo returns detailed information about a template func (vm *VersionManager) GetTemplateInfo(templateName string) (map[string]interface{}, error) { versions := vm.versions[templateName] if len(versions) == 0 { return nil, fmt.Errorf("template not found: %s", templateName) } history, _ := vm.GetVersionHistory(templateName) stats := vm.GetTemplateStats(templateName) diskUsage, _ := vm.GetDiskUsage() return map[string]interface{}{ "name": templateName, "stats": stats, "history": history, "disk_usage": diskUsage, "storage_path": vm.GetStoragePath(templateName), "retention": vm.GetRetentionPolicy(templateName), }, nil } // ExportTemplateMetadata exports template metadata to a file func (vm *VersionManager) ExportTemplateMetadata(exportPath string) error { // This could be implemented to export metadata // For now, just log the operation vm.logger.Info("template metadata export started", "export_path", exportPath) return nil } // ImportTemplateMetadata imports template metadata from a file func (vm *VersionManager) ImportTemplateMetadata(importPath string) error { // This could be implemented to import metadata // For now, just log the operation vm.logger.Info("template metadata import started", "import_path", importPath) return nil } // ValidateTemplateName validates a template name func (vm *VersionManager) ValidateTemplateName(name string) error { if name == "" { return fmt.Errorf("template name cannot be empty") } if len(name) > 50 { return fmt.Errorf("template name too long") } // Check for invalid characters invalidChars := []string{"/", "\\", ":", "*", "?", "\"", "<", ">", "|"} for _, char := range invalidChars { if containsString(name, char) { return fmt.Errorf("template name contains invalid character: %s", char) } } return nil } // Helper function to check if string contains substring func containsString(s, substr string) bool { for _, char := range substr { for _, c := range s { if c == char { return true } } } return false } // GetAllVersions returns all versions across all templates func (vm *VersionManager) GetAllVersions() map[string]interface{} { allVersions := make(map[string]interface{}) for templateName := range vm.versions { allVersions[templateName] = vm.versions[templateName] } return allVersions } // SearchVersions searches for versions matching criteria func (vm *VersionManager) SearchVersions(criteria map[string]interface{}) []VersionInfo { var results []VersionInfo for templateName := range vm.versions { versions := vm.versions[templateName] for _, version := range versions { if vm.matchesCriteria(version, criteria) { results = append(results, version) } } } return results } // matchesCriteria checks if a version matches search criteria func (vm *VersionManager) matchesCriteria(version VersionInfo, criteria map[string]interface{}) bool { for key, value := range criteria { switch key { case "author": if version.Author != value.(string) { return false } case "before": if !version.Timestamp.Before(value.(time.Time)) { return false } case "after": if !version.Timestamp.After(value.(time.Time)) { return false } case "description_contains": if !containsString(version.Description, value.(string)) { return false } } } return true } // GetVersionCount returns the number of versions for a template func (vm *VersionManager) GetVersionCount(templateName string) int { return len(vm.versions[templateName]) } // GetOldestVersion returns the oldest version for a template func (vm *VersionManager) GetOldestVersion(templateName string) (*VersionInfo, error) { versions := vm.versions[templateName] if len(versions) == 0 { return nil, fmt.Errorf("no versions found for template: %s", templateName) } return &versions[0], nil } // GetNewestVersion returns the newest version for a template func (vm *VersionManager) GetNewestVersion(templateName string) (*VersionInfo, error) { versions := vm.versions[templateName] if len(versions) == 0 { return nil, fmt.Errorf("no versions found for template: %s", templateName) } return &versions[len(versions)-1], nil } // GetActiveVersion returns the active version for a template func (vm *VersionManager) GetActiveVersion(templateName string) (*VersionInfo, error) { versions := vm.versions[templateName] for _, v := range versions { if v.Active { return &v, nil } } return nil, fmt.Errorf("no active version found for template: %s", templateName) } // SetActiveVersion sets a specific version as active func (vm *VersionManager) SetActiveVersion(templateName, version string) error { versions := vm.versions[templateName] for i := range versions { versions[i].Active = versions[i].Version == version } return nil } // GetVersionByHash returns a version by its hash func (vm *VersionManager) GetVersionByHash(templateName, hash string) (*VersionInfo, error) { versions := vm.versions[templateName] for _, v := range versions { if v.Hash == hash { return &v, nil } } return nil, fmt.Errorf("version with hash %s not found for template %s", hash, templateName) } // GetVersionsByDateRange returns versions within a date range func (vm *VersionManager) GetVersionsByDateRange(templateName string, start, end time.Time) []VersionInfo { versions := vm.versions[templateName] var results []VersionInfo for _, version := range versions { if version.Timestamp.After(start) && version.Timestamp.Before(end) { results = append(results, version) } } return results } // GetTemplateUsageStats returns usage statistics for templates func (vm *VersionManager) GetTemplateUsageStats() map[string]interface{} { stats := make(map[string]interface{}) totalVersions := 0 totalSize := int64(0) for templateName, versions := range vm.versions { for _, v := range versions { totalVersions++ totalSize += v.Size } stats[templateName] = map[string]interface{}{ "versions": len(versions), "size": totalSize, } } stats["total_templates"] = len(vm.versions) stats["total_versions"] = totalVersions stats["total_size"] = totalSize return stats } // ArchiveTemplate archives a template and all its versions func (vm *VersionManager) ArchiveTemplate(templateName string) error { // This could be implemented to archive templates // For now, just log the operation vm.logger.Info("template archived", "template", templateName) return nil } // RestoreTemplate restores an archived template func (vm *VersionManager) RestoreTemplate(templateName string) error { // This could be implemented to restore templates // For now, just log the operation vm.logger.Info("template restored", "template", templateName) return nil } // GetTemplateBackup creates a backup of all template versions func (vm *VersionManager) GetTemplateBackup() (*VersionHistory, error) { // This is a placeholder for backup functionality return nil, nil } // SetVersionLabel sets a label for a specific version func (vm *VersionManager) SetVersionLabel(templateName, version, label string) error { // This could be implemented to add labels to versions vm.logger.Info("version label set", "template", templateName, "version", version, "label", label) return nil } // GetVersionLabel returns the label for a specific version func (vm *VersionManager) GetVersionLabel(templateName, version string) string { // This could be implemented to get labels from versions return "" } // GetVersionTags returns tags for a specific version func (vm *VersionManager) GetVersionTags(templateName, version string) []string { // This could be implemented to get tags from versions return []string{} } // AddVersionTag adds a tag to a specific version func (vm *VersionManager) AddVersionTag(templateName, version, tag string) error { // This could be implemented to add tags to versions vm.logger.Info("version tag added", "template", templateName, "version", version, "tag", tag) return nil } // RemoveVersionTag removes a tag from a specific version func (vm *VersionManager) RemoveVersionTag(templateName, version, tag string) error { // This could be implemented to remove tags from versions vm.logger.Info("version tag removed", "template", templateName, "version", version, "tag", tag) return nil } // GetVersionComments returns comments for a specific version func (vm *VersionManager) GetVersionComments(templateName, version string) []string { // This could be implemented to get comments from versions return []string{} } // AddVersionComment adds a comment to a specific version func (vm *VersionManager) AddVersionComment(templateName, version, comment string) error { // This could be implemented to add comments to versions vm.logger.Info("version comment added", "template", templateName, "version", version, "comment", comment) return nil } // RemoveVersionComment removes a comment from a specific version func (vm *VersionManager) RemoveVersionComment(templateName, version, comment string) error { // This could be implemented to remove comments from versions vm.logger.Info("version comment removed", "template", templateName, "version", version, "comment", comment) return nil } // GetVersionAuthor returns the author of a specific version func (vm *VersionManager) GetVersionAuthor(templateName, version string) string { versions := vm.versions[templateName] for _, v := range versions { if v.Version == version { return v.Author } } return "" } // GetVersionDescription returns the description of a specific version func (vm *VersionManager) GetVersionDescription(templateName, version string) string { versions := vm.versions[templateName] for _, v := range versions { if v.Version == version { return v.Description } } return "" } // GetVersionTimestamp returns the timestamp of a specific version func (vm *VersionManager) GetVersionTimestamp(templateName, version string) (time.Time, error) { versions := vm.versions[templateName] for _, v := range versions { if v.Version == version { return v.Timestamp, nil } } return time.Time{}, fmt.Errorf("version %s not found for template %s", version, templateName) } // GetVersionFilePath returns the file path of a specific version func (vm *VersionManager) GetVersionFilePath(templateName, version string) (string, error) { versions := vm.versions[templateName] for _, v := range versions { if v.Version == version { return v.FilePath, nil } } return "", fmt.Errorf("version %s not found for template %s", version, templateName) } // GetVersionSize returns the size of a specific version func (vm *VersionManager) GetVersionSize(templateName, version string) (int64, error) { versions := vm.versions[templateName] for _, v := range versions { if v.Version == version { return v.Size, nil } } return 0, fmt.Errorf("version %s not found for template %s", version, templateName) } // GetVersionHash returns the hash of a specific version func (vm *VersionManager) GetVersionHash(templateName, version string) (string, error) { versions := vm.versions[templateName] for _, v := range versions { if v.Version == version { return v.Hash, nil } } return "", fmt.Errorf("version %s not found for template %s", version, templateName) } // IsVersionActive returns whether a specific version is active func (vm *VersionManager) IsVersionActive(templateName, version string) (bool, error) { versions := vm.versions[templateName] for _, v := range versions { if v.Version == version { return v.Active, nil } } return false, fmt.Errorf("version %s not found for template %s", version, templateName) } // GetTotalDiskUsage returns the total disk usage across all templates func (vm *VersionManager) GetTotalDiskUsage() int64 { total := int64(0) for _, versions := range vm.versions { for _, v := range versions { total += v.Size } } return total } // GetVersionSummary returns a summary of all versions func (vm *VersionManager) GetVersionSummary() map[string]interface{} { summary := map[string]interface{}{ "total_templates": len(vm.versions), "total_versions": vm.getTotalVersionCount(), "total_size": vm.GetTotalDiskUsage(), "templates": make(map[string]int), } for templateName, versions := range vm.versions { summary["templates"].(map[string]int)[templateName] = len(versions) } return summary } // getTotalVersionCount returns the total number of versions across all templates func (vm *VersionManager) getTotalVersionCount() int { count := 0 for _, versions := range vm.versions { count += len(versions) } return count } // GetVersionHistorySummary returns a summary of version history func (vm *VersionManager) GetVersionHistorySummary(templateName string) map[string]interface{} { versions := vm.versions[templateName] if len(versions) == 0 { return map[string]interface{}{ "template_name": templateName, "total_versions": 0, "current_version": "", "oldest_version": "", "newest_version": "", } } return map[string]interface{}{ "template_name": templateName, "total_versions": len(versions), "current_version": versions[len(versions)-1].Version, "oldest_version": versions[0].Version, "newest_version": versions[len(versions)-1].Version, "first_created": versions[0].Timestamp, "last_updated": versions[len(versions)-1].Timestamp, "total_size": func() int64 { total := int64(0) for _, v := range versions { total += v.Size } return total }(), } } // GetTemplateVersionHistory returns the complete version history for a template func (vm *VersionManager) GetTemplateVersionHistory(templateName string) (*VersionHistory, error) { versions := vm.versions[templateName] if len(versions) == 0 { return nil, fmt.Errorf("no versions found for template: %s", templateName) } var current VersionInfo for _, v := range versions { if v.Active { current = v break } } // If no active version, use the latest if current.Version == "" { current = versions[len(versions)-1] } return &VersionHistory{ TemplateName: templateName, Versions: versions, Current: current, }, nil }