Spaces:
Running
Running
| from dataclasses import dataclass, asdict | |
| from typing import List, Dict, Any, Optional, Tuple | |
| import uuid | |
| import json | |
| from pathlib import Path | |
| from config import DATA_FILE | |
| from utils import normalize_skill_list | |
| # ----------------------------- | |
| # Profile Data Class | |
| # ----------------------------- | |
| class Profile: | |
| id: str | |
| username: str | |
| offers: List[str] | |
| wants: List[str] | |
| availability: str | |
| preferences: str | |
| avatar: Optional[str] = None | |
| def from_dict(d: Dict[str, Any]) -> "Profile": | |
| return Profile( | |
| id=str(d.get("id") or uuid.uuid4()), | |
| username=str(d.get("username") or "").strip(), | |
| offers=normalize_skill_list( | |
| ",".join(d.get("offers", [])) | |
| if isinstance(d.get("offers"), list) | |
| else d.get("offers") | |
| ), | |
| wants=normalize_skill_list( | |
| ",".join(d.get("wants", [])) | |
| if isinstance(d.get("wants"), list) | |
| else d.get("wants") | |
| ), | |
| availability=str(d.get("availability") or ""), | |
| preferences=str(d.get("preferences") or ""), | |
| avatar=d.get("avatar"), | |
| ) | |
| def to_dict(self) -> Dict[str, Any]: | |
| return asdict(self) | |
| # ----------------------------- | |
| # ProfileStore | |
| # ----------------------------- | |
| class ProfileStore: | |
| def __init__(self, path: Path = DATA_FILE) -> None: | |
| self.path = path | |
| self._ensure_file() | |
| def _ensure_file(self) -> None: | |
| if not self.path.exists(): | |
| self.path.write_text("[]", encoding="utf-8") | |
| def load_all(self) -> List[Profile]: | |
| try: | |
| data = json.loads(self.path.read_text(encoding="utf-8")) | |
| return [Profile.from_dict(d) for d in data if isinstance(d, dict)] | |
| except json.JSONDecodeError: | |
| return [] | |
| def save_all(self, profiles: List[Profile]) -> None: | |
| self.path.write_text( | |
| json.dumps([p.to_dict() for p in profiles], indent=2, ensure_ascii=False), | |
| encoding="utf-8", | |
| ) | |
| def find_by_username(self, username: str) -> Optional[Profile]: | |
| username = (username or "").strip() | |
| for p in self.load_all(): | |
| if p.username.lower() == username.lower(): | |
| return p | |
| return None | |
| def add_or_update(self, profile: Profile) -> Tuple[bool, str]: | |
| ok, err = self.validate_profile(profile) | |
| if not ok: | |
| return False, f"Validation failed: {err}" | |
| profiles = self.load_all() | |
| existing = next((p for p in profiles if p.username.lower() == profile.username.lower()), None) | |
| if existing: | |
| existing.offers = profile.offers | |
| existing.wants = profile.wants | |
| existing.availability = profile.availability | |
| existing.preferences = profile.preferences | |
| existing.avatar = profile.avatar | |
| self.save_all(profiles) | |
| return True, "Profile updated." | |
| profile.id = profile.id or str(uuid.uuid4()) | |
| profiles.append(profile) | |
| self.save_all(profiles) | |
| return True, "Profile created." | |
| def delete(self, username: str) -> Tuple[bool, str]: | |
| username = (username or "").strip() | |
| profiles = self.load_all() | |
| profiles_new = [p for p in profiles if p.username.lower() != username.lower()] | |
| if len(profiles_new) == len(profiles): | |
| return False, "Profile not found." | |
| self.save_all(profiles_new) | |
| return True, "Profile deleted." | |
| def validate_profile(profile: Profile) -> Tuple[bool, Optional[str]]: | |
| if not profile.username: | |
| return False, "Username is required." | |
| if len(profile.username) > 60: | |
| return False, "Username must be 60 characters or fewer." | |
| if not profile.offers and not profile.wants: | |
| return False, "At least one offer or want is required." | |
| for s in profile.offers + profile.wants: | |
| if not s or len(s) > 120: | |
| return False, "Invalid skill entry." | |
| return True, None | |