File size: 6,782 Bytes
8dafdf7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import os
import shutil
import json
import tarfile
from datetime import datetime
from typing import Dict, Any, List
from bson import ObjectId
from ..db.database import db
from ..utils.logger import logger

class BackupService:
    def __init__(self):
        self.backup_dir = "backups"
        self._ensure_backup_dir()

    def _ensure_backup_dir(self):
        """Ensure backup directory exists"""
        if not os.path.exists(self.backup_dir):
            os.makedirs(self.backup_dir)

    async def create_backup(self, include_files: bool = True) -> Dict[str, Any]:
        """Create a new system backup"""
        try:
            timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S")
            backup_id = str(ObjectId())
            backup_name = f"backup_{timestamp}_{backup_id}"
            backup_path = os.path.join(self.backup_dir, backup_name)
            
            # Create backup directory
            os.makedirs(backup_path, exist_ok=True)
            
            # Backup database collections
            db_backup = {}
            for collection in await db.db.list_collection_names():
                docs = await db.db[collection].find().to_list(None)
                db_backup[collection] = [
                    {**doc, "_id": str(doc["_id"])}
                    for doc in docs
                ]
            
            # Save database backup
            with open(os.path.join(backup_path, "database.json"), "w") as f:
                json.dump(db_backup, f, default=str)
            
            # Backup files if requested
            if include_files:
                uploads_dir = "uploads"
                if os.path.exists(uploads_dir):
                    shutil.copytree(
                        uploads_dir,
                        os.path.join(backup_path, "uploads"),
                        dirs_exist_ok=True
                    )
            
            # Create archive
            archive_path = f"{backup_path}.tar.gz"
            with tarfile.open(archive_path, "w:gz") as tar:
                tar.add(backup_path, arcname=os.path.basename(backup_path))
            
            # Clean up temporary directory
            shutil.rmtree(backup_path)
            
            # Record backup in database
            backup_info = {
                "_id": backup_id,
                "filename": f"{backup_name}.tar.gz",
                "path": archive_path,
                "created_at": datetime.utcnow(),
                "size": os.path.getsize(archive_path),
                "includes_files": include_files
            }
            
            await db.db["backup_history"].insert_one(backup_info)
            
            return {
                "id": backup_id,
                "path": archive_path,
                "size": backup_info["size"],
                "created_at": backup_info["created_at"]
            }
            
        except Exception as e:
            logger.error(f"Backup creation failed: {str(e)}")
            raise

    async def restore_backup(self, backup_path: str) -> Dict[str, Any]:
        """Restore system from a backup"""
        try:
            if not os.path.exists(backup_path):
                raise FileNotFoundError("Backup file not found")
            
            # Create temporary restoration directory
            restore_dir = f"restore_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}"
            os.makedirs(restore_dir, exist_ok=True)
            
            # Extract archive
            with tarfile.open(backup_path, "r:gz") as tar:
                tar.extractall(restore_dir)
            
            backup_contents = os.listdir(restore_dir)[0]
            backup_root = os.path.join(restore_dir, backup_contents)
            
            # Restore database
            with open(os.path.join(backup_root, "database.json"), "r") as f:
                db_backup = json.load(f)
            
            # Clear existing collections
            for collection in await db.db.list_collection_names():
                await db.db[collection].delete_many({})
            
            # Restore collections
            for collection, docs in db_backup.items():
                if docs:
                    # Convert string IDs back to ObjectId
                    for doc in docs:
                        doc["_id"] = ObjectId(doc["_id"])
                    await db.db[collection].insert_many(docs)
            
            # Restore files if present
            uploads_source = os.path.join(backup_root, "uploads")
            if os.path.exists(uploads_source):
                if os.path.exists("uploads"):
                    shutil.rmtree("uploads")
                shutil.copytree(uploads_source, "uploads")
            
            # Clean up
            shutil.rmtree(restore_dir)
            
            return {
                "success": True,
                "collections_restored": len(db_backup),
                "files_restored": os.path.exists(uploads_source)
            }
            
        except Exception as e:
            logger.error(f"Backup restoration failed: {str(e)}")
            raise
        finally:
            if os.path.exists(restore_dir):
                shutil.rmtree(restore_dir)

    async def list_backups(self) -> List[Dict[str, Any]]:
        """List all available backups"""
        try:
            backups = await db.db["backup_history"].find().sort("created_at", -1).to_list(None)
            return [
                {
                    "id": str(backup["_id"]),
                    "filename": backup["filename"],
                    "created_at": backup["created_at"],
                    "size": backup["size"],
                    "includes_files": backup["includes_files"]
                }
                for backup in backups
            ]
        except Exception as e:
            logger.error(f"Failed to list backups: {str(e)}")
            raise

    async def delete_backup(self, backup_id: str) -> bool:
        """Delete a backup"""
        try:
            backup = await db.db["backup_history"].find_one({"_id": backup_id})
            if not backup:
                return False
            
            # Delete the physical backup file
            if os.path.exists(backup["path"]):
                os.remove(backup["path"])
            
            # Remove from database
            await db.db["backup_history"].delete_one({"_id": backup_id})
            return True
            
        except Exception as e:
            logger.error(f"Failed to delete backup: {str(e)}")
            raise

backup = BackupService()