From 89eaef4070b97d619ea4f396d1cc80cf74feda43 Mon Sep 17 00:00:00 2001 From: novaardiansyah Date: Sat, 25 Jul 2026 12:56:49 +0700 Subject: [PATCH] feat(backup): complete backup feature with API endpoint delivery - Implement backup process execution - Send backup results to backend API endpoint - Handle success and failure responses --- .gitignore | 3 +++ api/report.py | 22 ++++++++++++++++++++++ app.py | 4 ++++ backup/backup.py | 33 +++++++++++++++++++++++++++++++++ backup/zip.py | 8 ++++++++ config.py | 11 +++++++++++ models/backup_result.py | 15 +++++++++++++++ requirements.txt | 7 +++++++ utils/checksum.py | 8 ++++++++ windows/process-info.py | 5 +++++ 10 files changed, 116 insertions(+) create mode 100644 api/report.py create mode 100644 backup/backup.py create mode 100644 backup/zip.py create mode 100644 config.py create mode 100644 models/backup_result.py create mode 100644 requirements.txt create mode 100644 utils/checksum.py create mode 100644 windows/process-info.py diff --git a/.gitignore b/.gitignore index cbe8d41..1a522d0 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ AGENTS.md .env.production .env.local .env.staging + +__pycache__ +backups/ \ No newline at end of file diff --git a/api/report.py b/api/report.py new file mode 100644 index 0000000..b8521f3 --- /dev/null +++ b/api/report.py @@ -0,0 +1,22 @@ +import requests + +from config import ( + API_URL, + API_KEY +) + +def send_backup(result): + headers = { + "Authorization": f"Bearer {API_KEY}", + "Accept": "application/json" + } + + response = requests.post( + f"{API_URL}/backups", + headers=headers, + json=result.__dict__ + ) + + response.raise_for_status() + + return response.json() \ No newline at end of file diff --git a/app.py b/app.py index e69de29..f4dc551 100644 --- a/app.py +++ b/app.py @@ -0,0 +1,4 @@ +from config import TEST_BACKUP_PATH +from backup.backup import run_backup + +run_backup(TEST_BACKUP_PATH) diff --git a/backup/backup.py b/backup/backup.py new file mode 100644 index 0000000..d137320 --- /dev/null +++ b/backup/backup.py @@ -0,0 +1,33 @@ +from datetime import datetime +from pathlib import Path + +from backup.zip import create_zip +from models.backup_result import BackupResult +from utils.checksum import sha256 +from api.report import send_backup +from config import BACKUP_OUTPUT + +def run_backup(source_path: str): + started = datetime.now() + filename = f"backup-{started.strftime('%Y%m%d-%H%M%S')}" + + destination = str(Path(BACKUP_OUTPUT) / filename) + zip_file = create_zip(source_path, destination) + completed = datetime.now() + + result = BackupResult( + file_name=Path(zip_file).name, + file_path=zip_file, + file_size=Path(zip_file).stat().st_size, + checksum=sha256(zip_file), + started_at=started.isoformat(), + completed_at=completed.isoformat(), + duration=int((completed - started).total_seconds()), + status="success", + type="files", + message=None + ) + + response = send_backup(result) + + print(response) diff --git a/backup/zip.py b/backup/zip.py new file mode 100644 index 0000000..e3fca0c --- /dev/null +++ b/backup/zip.py @@ -0,0 +1,8 @@ +import shutil +from pathlib import Path + +def create_zip(source_path: str, destination: str) -> str: + dest_path = Path(destination) + dest_path.parent.mkdir(parents=True, exist_ok=True) + archive_path = shutil.make_archive(destination, "zip", source_path) + return archive_path diff --git a/config.py b/config.py new file mode 100644 index 0000000..4b4a2e7 --- /dev/null +++ b/config.py @@ -0,0 +1,11 @@ +from dotenv import load_dotenv + +import os + +load_dotenv() + +APP_NAME = os.getenv("APP_NAME") +API_URL = os.getenv("API_URL") +API_KEY = os.getenv("API_KEY") +BACKUP_OUTPUT = os.getenv("BACKUP_OUTPUT", "backups") +TEST_BACKUP_PATH = os.getenv("TEST_BACKUP_PATH") diff --git a/models/backup_result.py b/models/backup_result.py new file mode 100644 index 0000000..0a0a8a7 --- /dev/null +++ b/models/backup_result.py @@ -0,0 +1,15 @@ +from dataclasses import dataclass + +@dataclass +class BackupResult: + file_name: str + file_path: str + file_size: int + checksum: str + started_at: str + completed_at: str + duration: int + status: str + type: str + message: str | None + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..20bcc84 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +certifi==2026.7.22 +charset-normalizer==3.4.9 +idna==3.18 +psutil==7.2.2 +python-dotenv==1.2.2 +requests==2.34.2 +urllib3==2.7.0 diff --git a/utils/checksum.py b/utils/checksum.py new file mode 100644 index 0000000..faf6a49 --- /dev/null +++ b/utils/checksum.py @@ -0,0 +1,8 @@ +import hashlib + +def sha256(filepath): + hash = hashlib.sha256() + with open(filepath, "rb") as file: + while chunk := file.read(8192): + hash.update(chunk) + return hash.hexdigest() \ No newline at end of file diff --git a/windows/process-info.py b/windows/process-info.py new file mode 100644 index 0000000..7538605 --- /dev/null +++ b/windows/process-info.py @@ -0,0 +1,5 @@ +import psutil + +for process in psutil.process_iter(["pid", "name"]): + print(process.info) + \ No newline at end of file