mirror of
https://github.com/srbhr/Resume-Matcher.git
synced 2026-01-15 03:58:14 +00:00
- 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.
216 lines
6.6 KiB
Python
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()
|