Improved features, implemented moco integration
This commit is contained in:
@@ -11,7 +11,7 @@ from src.database.models import (
|
||||
LinkedInProfile, LinkedInPost, Topic,
|
||||
ProfileAnalysis, ResearchResult, GeneratedPost, PostType,
|
||||
User, Profile, Company, Invitation, ExamplePost, ReferenceProfile,
|
||||
ApiUsageLog, LicenseKey, CompanyDailyQuota
|
||||
ApiUsageLog, LicenseKey, CompanyDailyQuota, LicenseKeyOffer
|
||||
)
|
||||
|
||||
|
||||
@@ -1252,6 +1252,59 @@ class DatabaseClient:
|
||||
)
|
||||
logger.info(f"Deleted license key: {key_id}")
|
||||
|
||||
# ==================== LICENSE KEY OFFERS ====================
|
||||
|
||||
async def create_license_key_offer(
|
||||
self,
|
||||
license_key_id: UUID,
|
||||
moco_offer_id: int,
|
||||
moco_offer_identifier: Optional[str],
|
||||
moco_offer_url: Optional[str],
|
||||
offer_title: Optional[str],
|
||||
company_name: Optional[str],
|
||||
price: Optional[float],
|
||||
payment_frequency: Optional[str],
|
||||
) -> "LicenseKeyOffer":
|
||||
"""Save a MOCO offer linked to a license key."""
|
||||
data = {
|
||||
"license_key_id": str(license_key_id),
|
||||
"moco_offer_id": moco_offer_id,
|
||||
"moco_offer_identifier": moco_offer_identifier,
|
||||
"moco_offer_url": moco_offer_url,
|
||||
"offer_title": offer_title,
|
||||
"company_name": company_name,
|
||||
"price": price,
|
||||
"payment_frequency": payment_frequency,
|
||||
"status": "draft",
|
||||
}
|
||||
result = await asyncio.to_thread(
|
||||
lambda: self.client.table("license_key_offers").insert(data).execute()
|
||||
)
|
||||
return LicenseKeyOffer(**result.data[0])
|
||||
|
||||
async def list_license_key_offers(self, license_key_id: UUID) -> list["LicenseKeyOffer"]:
|
||||
"""List all MOCO offers for a license key."""
|
||||
result = await asyncio.to_thread(
|
||||
lambda: self.client.table("license_key_offers")
|
||||
.select("*")
|
||||
.eq("license_key_id", str(license_key_id))
|
||||
.order("created_at", desc=True)
|
||||
.execute()
|
||||
)
|
||||
return [LicenseKeyOffer(**row) for row in result.data]
|
||||
|
||||
async def update_license_key_offer_status(
|
||||
self, offer_id: UUID, status: str
|
||||
) -> "LicenseKeyOffer":
|
||||
"""Update the status of a stored offer."""
|
||||
result = await asyncio.to_thread(
|
||||
lambda: self.client.table("license_key_offers")
|
||||
.update({"status": status})
|
||||
.eq("id", str(offer_id))
|
||||
.execute()
|
||||
)
|
||||
return LicenseKeyOffer(**result.data[0])
|
||||
|
||||
# ==================== COMPANY QUOTAS ====================
|
||||
|
||||
async def get_company_daily_quota(
|
||||
@@ -1356,6 +1409,66 @@ class DatabaseClient:
|
||||
|
||||
return True, ""
|
||||
|
||||
# ==================== EMAIL ACTION TOKENS ====================
|
||||
|
||||
async def create_email_token(self, token: str, post_id: UUID, action: str, expires_hours: int = 72) -> None:
|
||||
"""Store an email action token in the database."""
|
||||
from datetime import timedelta
|
||||
expires_at = datetime.now(timezone.utc) + timedelta(hours=expires_hours)
|
||||
data = {
|
||||
"token": token,
|
||||
"post_id": str(post_id),
|
||||
"action": action,
|
||||
"expires_at": expires_at.isoformat(),
|
||||
"used": False,
|
||||
}
|
||||
await asyncio.to_thread(
|
||||
lambda: self.client.table("email_action_tokens").insert(data).execute()
|
||||
)
|
||||
logger.debug(f"Created email token for post {post_id} action={action}")
|
||||
|
||||
async def get_email_token(self, token: str) -> Optional[Dict[str, Any]]:
|
||||
"""Retrieve email token data; returns None if not found."""
|
||||
result = await asyncio.to_thread(
|
||||
lambda: self.client.table("email_action_tokens").select("*").eq("token", token).execute()
|
||||
)
|
||||
if not result.data:
|
||||
return None
|
||||
return result.data[0]
|
||||
|
||||
async def mark_email_token_used(self, token: str) -> None:
|
||||
"""Mark an email token as used."""
|
||||
await asyncio.to_thread(
|
||||
lambda: self.client.table("email_action_tokens").update({"used": True}).eq("token", token).execute()
|
||||
)
|
||||
|
||||
async def cleanup_expired_email_tokens(self) -> None:
|
||||
"""Delete expired email tokens from the database."""
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
result = await asyncio.to_thread(
|
||||
lambda: self.client.table("email_action_tokens").delete().lt("expires_at", now).execute()
|
||||
)
|
||||
count = len(result.data) if result.data else 0
|
||||
if count:
|
||||
logger.info(f"Cleaned up {count} expired email tokens")
|
||||
|
||||
# ==================== LINKEDIN TOKEN REFRESH ====================
|
||||
|
||||
async def get_expiring_linkedin_accounts(self, within_days: int = 7) -> list:
|
||||
"""Return active LinkedIn accounts whose tokens expire within within_days and have a refresh_token."""
|
||||
from src.database.models import LinkedInAccount
|
||||
from datetime import timedelta
|
||||
cutoff = (datetime.now(timezone.utc) + timedelta(days=within_days)).isoformat()
|
||||
result = await asyncio.to_thread(
|
||||
lambda: self.client.table("linkedin_accounts")
|
||||
.select("*")
|
||||
.eq("is_active", True)
|
||||
.lt("token_expires_at", cutoff)
|
||||
.not_.is_("refresh_token", "null")
|
||||
.execute()
|
||||
)
|
||||
return [LinkedInAccount(**row) for row in result.data]
|
||||
|
||||
|
||||
# Global database client instance
|
||||
db = DatabaseClient()
|
||||
|
||||
@@ -376,6 +376,21 @@ class LicenseKey(DBModel):
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class LicenseKeyOffer(DBModel):
|
||||
"""MOCO offer created for a license key."""
|
||||
id: Optional[UUID] = None
|
||||
license_key_id: UUID
|
||||
moco_offer_id: int
|
||||
moco_offer_identifier: Optional[str] = None
|
||||
moco_offer_url: Optional[str] = None
|
||||
offer_title: Optional[str] = None
|
||||
company_name: Optional[str] = None
|
||||
price: Optional[float] = None
|
||||
payment_frequency: Optional[str] = None
|
||||
status: str = "draft"
|
||||
created_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class CompanyDailyQuota(DBModel):
|
||||
"""Daily usage quota for a company."""
|
||||
id: Optional[UUID] = None
|
||||
|
||||
Reference in New Issue
Block a user