Files
Resume-Matcher/apps/backend/app/database.py
srbhr 3b53f6c20b feat(config, database, settings): add API key and database reset features
- Implemented clear_all_api_keys function to remove all API keys from the config.
- Added reset_database function to truncate all tables and clear uploads.
- Created API endpoints for clearing API keys and resetting the database.
- Enhanced settings page with options to clear API keys and reset the database, including confirmation dialogs.
- Updated translations for new features in multiple languages.
2026-01-06 21:44:35 +05:30

216 lines
6.6 KiB
Python

"""TinyDB database layer for JSON storage."""
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from uuid import uuid4
from tinydb import Query, TinyDB
from tinydb.table import Table
from app.config import settings
class Database:
"""TinyDB wrapper for resume matcher data."""
def __init__(self, db_path: Path | None = None):
self.db_path = db_path or settings.db_path
self.db_path.parent.mkdir(parents=True, exist_ok=True)
self._db: TinyDB | None = None
@property
def db(self) -> TinyDB:
"""Lazy initialization of TinyDB instance."""
if self._db is None:
self._db = TinyDB(self.db_path)
return self._db
@property
def resumes(self) -> Table:
"""Resumes table."""
return self.db.table("resumes")
@property
def jobs(self) -> Table:
"""Job descriptions table."""
return self.db.table("jobs")
@property
def improvements(self) -> Table:
"""Improvement results table."""
return self.db.table("improvements")
def close(self) -> None:
"""Close database connection."""
if self._db is not None:
self._db.close()
self._db = None
# Resume operations
def create_resume(
self,
content: str,
content_type: str = "md",
filename: str | None = None,
is_master: bool = False,
parent_id: str | None = None,
processed_data: dict[str, Any] | None = None,
processing_status: str = "pending",
cover_letter: str | None = None,
outreach_message: str | None = None,
) -> dict[str, Any]:
"""Create a new resume entry.
processing_status: "pending", "processing", "ready", "failed"
"""
resume_id = str(uuid4())
now = datetime.now(timezone.utc).isoformat()
doc = {
"resume_id": resume_id,
"content": content,
"content_type": content_type,
"filename": filename,
"is_master": is_master,
"parent_id": parent_id,
"processed_data": processed_data,
"processing_status": processing_status,
"cover_letter": cover_letter,
"outreach_message": outreach_message,
"created_at": now,
"updated_at": now,
}
self.resumes.insert(doc)
return doc
def get_resume(self, resume_id: str) -> dict[str, Any] | None:
"""Get resume by ID."""
Resume = Query()
result = self.resumes.search(Resume.resume_id == resume_id)
return result[0] if result else None
def get_master_resume(self) -> dict[str, Any] | None:
"""Get the master resume if exists."""
Resume = Query()
result = self.resumes.search(Resume.is_master == True)
return result[0] if result else None
def update_resume(
self, resume_id: str, updates: dict[str, Any]
) -> dict[str, Any] | None:
"""Update resume by ID."""
Resume = Query()
updates["updated_at"] = datetime.now(timezone.utc).isoformat()
self.resumes.update(updates, Resume.resume_id == resume_id)
return self.get_resume(resume_id)
def delete_resume(self, resume_id: str) -> bool:
"""Delete resume by ID."""
Resume = Query()
removed = self.resumes.remove(Resume.resume_id == resume_id)
return len(removed) > 0
def list_resumes(self) -> list[dict[str, Any]]:
"""List all resumes."""
return list(self.resumes.all())
def set_master_resume(self, resume_id: str) -> bool:
"""Set a resume as the master, unsetting any existing master."""
Resume = Query()
# Unset current master
self.resumes.update({"is_master": False}, Resume.is_master == True)
# Set new master
updated = self.resumes.update(
{"is_master": True}, Resume.resume_id == resume_id
)
return len(updated) > 0
# Job operations
def create_job(
self, content: str, resume_id: str | None = None
) -> dict[str, Any]:
"""Create a new job description entry."""
job_id = str(uuid4())
now = datetime.now(timezone.utc).isoformat()
doc = {
"job_id": job_id,
"content": content,
"resume_id": resume_id,
"created_at": now,
}
self.jobs.insert(doc)
return doc
def get_job(self, job_id: str) -> dict[str, Any] | None:
"""Get job by ID."""
Job = Query()
result = self.jobs.search(Job.job_id == job_id)
return result[0] if result else None
# Improvement operations
def create_improvement(
self,
original_resume_id: str,
tailored_resume_id: str,
job_id: str,
improvements: list[dict[str, Any]],
) -> dict[str, Any]:
"""Create an improvement result entry."""
request_id = str(uuid4())
now = datetime.now(timezone.utc).isoformat()
doc = {
"request_id": request_id,
"original_resume_id": original_resume_id,
"tailored_resume_id": tailored_resume_id,
"job_id": job_id,
"improvements": improvements,
"created_at": now,
}
self.improvements.insert(doc)
return doc
def get_improvement_by_tailored_resume(
self, tailored_resume_id: str
) -> dict[str, Any] | None:
"""Get improvement record by tailored resume ID.
This is used to retrieve the job context for on-demand
cover letter and outreach message generation.
"""
Improvement = Query()
result = self.improvements.search(
Improvement.tailored_resume_id == tailored_resume_id
)
return result[0] if result else None
# Stats
def get_stats(self) -> dict[str, Any]:
"""Get database statistics."""
return {
"total_resumes": len(self.resumes),
"total_jobs": len(self.jobs),
"total_improvements": len(self.improvements),
"has_master_resume": self.get_master_resume() is not None,
}
def reset_database(self) -> None:
"""Reset the database by truncating all tables and clearing uploads."""
# Truncate tables
self.resumes.truncate()
self.jobs.truncate()
self.improvements.truncate()
# Clear uploads directory
uploads_dir = settings.data_dir / "uploads"
if uploads_dir.exists():
import shutil
shutil.rmtree(uploads_dir)
uploads_dir.mkdir(parents=True, exist_ok=True)
# Global database instance
db = Database()