ibuilder / services /storage_service.py
Soufianesejjari's picture
Add experience management and skill categorization to profile model
9bc9256
"""
Service for storing and retrieving profile data using SQLite
"""
import sqlite3
from models import Profile, Skill, Project, Education, SocialMedia
from config import get_settings
import json
import logging
from typing import Dict, Any, Optional, List
import requests
settings = get_settings()
logger = logging.getLogger(__name__)
class StorageService:
"""Service for storing and retrieving profile data using SQLite"""
def __init__(self):
self.db_path = settings.SQLITE_DB_PATH
self.external_api_url = settings.EXTERNAL_API_URL
self._create_table()
def _create_table(self):
"""Create the profiles table if it doesn't exist"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS profiles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
title TEXT NOT NULL,
email TEXT NOT NULL,
bio TEXT NOT NULL,
tagline TEXT,
social TEXT,
profileImg TEXT,
projects TEXT,
skills TEXT,
educations TEXT,
experiences TEXT
)
""")
conn.commit()
conn.close()
logger.info("Profiles table created or already exists")
except Exception as e:
logger.error(f"Error creating table: {e}")
raise
def profile_to_dict(self, profile: Profile) -> Dict[str, Any]:
"""Convert Profile object to dictionary for SQLite storage"""
return {
"name": profile.name,
"title": profile.title,
"email": profile.email,
"bio": profile.bio,
"tagline": profile.tagline if profile.tagline else None,
"social": json.dumps({
"linkedin": profile.social.linkedin if profile.social else None,
"github": profile.social.github if profile.social else None,
"instagram": profile.social.instagram if profile.social else None
}),
"profileImg": profile.profileImg,
"projects": json.dumps([
{
"title": project.title,
"description": project.description,
"techStack": project.techStack,
"githubUrl": project.githubUrl,
"demoUrl": project.demoUrl
} for project in profile.projects
]),
"skills": json.dumps([
{
"name": skill.name,
"category": skill.category.value if skill.category else None,
"img": skill.img
} for skill in profile.skills
]),
"experiences": json.dumps([
{
"company": exp.company,
"position": exp.position,
"startDate": exp.startDate,
"endDate": exp.endDate,
"description": exp.description
} for exp in profile.experiences
]),
"educations": json.dumps([
{
"school": edu.school,
"degree": edu.degree,
"fieldOfStudy": edu.fieldOfStudy,
"startDate": edu.startDate,
"endDate": edu.endDate
} for edu in profile.educations
])
}
def send_to_external_api(self, profile_data: Dict[str, Any], profile_id: str) -> Dict[str, Any]:
"""
Send profile data to the external API
Args:
profile_data: Dictionary containing profile data
profile_id: ID of the stored profile
Returns:
Response from the external API or error details
"""
try:
if not self.external_api_url:
logger.warning("EXTERNAL_API_URL is not configured, skipping external API sync")
return {"success": False, "reason": "External API URL not configured"}
# Add the ID to the profile data
profile_data["id"] = profile_id
# Send the data to the external API
response = requests.post(
f"{self.external_api_url}/profiles",
json=profile_data,
headers={"Content-Type": "application/json"}
)
# Check if the request was successful
if response.status_code in (200, 201):
logger.info(f"Profile successfully sent to external API")
return {
"success": True,
"external_id": response.json().get("id", None),
"external_url": f"{self.external_api_url}/profiles/{profile_id}"
}
else:
logger.error(f"Failed to send profile to external API: {response.status_code} - {response.text}")
return {
"success": False,
"status_code": response.status_code,
"message": response.text
}
except Exception as e:
logger.error(f"Error sending profile to external API: {e}")
return {
"success": False,
"exception": str(e)
}
def store_profile(self, profile: Profile, error_handler=None) -> str:
"""
Store profile data in SQLite
Args:
profile: The Profile object to store
error_handler: Optional function to handle errors (useful for framework-specific error handling)
Returns:
String ID of the stored profile
"""
profile_dict = self.profile_to_dict(profile)
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO profiles (name, title, email, bio, tagline, social, profileImg, projects, skills, educations, experiences)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
profile_dict["name"],
profile_dict["title"],
profile_dict["email"],
profile_dict["bio"],
profile_dict["tagline"],
profile_dict["social"],
profile_dict["profileImg"],
profile_dict["projects"],
profile_dict["skills"],
profile_dict["educations"],
profile_dict["experiences"]
))
conn.commit()
profile_id = str(cursor.lastrowid)
conn.close()
logger.info(f"Profile saved successfully with ID: {profile_id}")
# Send to external API
external_api_result = self.send_to_external_api(profile_dict, profile_id)
# Store the external API result in the session state for later use
import streamlit as st
if "st" in globals() and hasattr(st, "session_state"):
st.session_state.external_api_result = external_api_result
return profile_id
except Exception as e:
logger.error(f"SQLite error: {e}")
if error_handler:
error_handler(f"Error connecting to SQLite: {str(e)}")
return None
def get_profile(self, profile_id: int) -> Optional[Dict[str, Any]]:
"""
Retrieve a profile from SQLite by its ID
Args:
profile_id: The ID of the profile to retrieve
Returns:
A dictionary representing the profile, or None if not found
"""
try:
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT * FROM profiles WHERE id = ?", (profile_id,))
row = cursor.fetchone()
conn.close()
if row:
profile = {
"id": row[0],
"name": row[1],
"title": row[2],
"email": row[3],
"bio": row[4],
"tagline": row[5],
"social": json.loads(row[6]) if row[6] else None,
"profileImg": row[7],
"projects": json.loads(row[8]) if row[8] else [],
"skills": json.loads(row[9]) if row[9] else [],
"educations": json.loads(row[10]) if row[10] else [],
"experiences": json.loads(row[11]) if row[11] else []
}
logger.debug(f"Retrieved profile: {profile_id}")
return profile
else:
logger.warning(f"Profile not found: {profile_id}")
return None
except Exception as e:
logger.error(f"Error retrieving profile {profile_id}: {e}")
return None
# Create a global instance
storage_service = StorageService()