dograh/api/db/user_client.py

222 lines
8.8 KiB
Python
Raw Normal View History

import uuid
2025-09-09 14:37:32 +05:30
from datetime import datetime, timezone
from loguru import logger
from pydantic import ValidationError
from sqlalchemy import func
2025-09-09 14:37:32 +05:30
from sqlalchemy.future import select
from api.db.base_client import BaseDBClient
from api.db.models import UserConfigurationModel, UserModel
feat: UI refresh and user onboarding (#430) * docs: design spec for lead-gen surfaces (Credits & Billing, Hire-an-Expert, Top-up, Enterprise) Add brainstorming spec for: sidebar OBSERVE→MANAGE rename + Credits & Billing link + Hire-an-Expert footer button; new /billing page with extracted Dograh Model Credits card + CTAs; Top-up / Hire-an-Expert / Enterprise intake modals with inline math captcha; and a workflow-builder Hire-an-Expert nudge. Frontend only; submissions fire PostHog events via a submitLead() seam for a future MongoDB endpoint. Also gitignore .superpowers/ brainstorm mockups. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: implementation plan for user-onboarding lead-gen surfaces 14 bite-sized tasks: PostHog events, shared helpers (field options, work-email blocklist, submitLead seam, math captcha), three intake modals (enterprise/hire/top-up), LeadFormsProvider context, AppLayout mount, sidebar MANAGE rename + Credits & Billing link + footer Hire button, extracted DograhCreditsCard, /billing page, credits removal from Agent Runs, builder nudge, and a full verification/dogfood pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): register PostHog events for lead-gen surfaces Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): shared field options, work-email validation, and submit seam Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): inline math captcha field Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): enterprise intake modal Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): hire-an-expert modal with enterprise link Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): top-up modal with >20k volume-pricing gate Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): shared lead-forms context provider Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): mount LeadFormsProvider in app layout Wrap the sidebar branch of AppLayout with LeadFormsProvider so the shared lead modals are available to the sidebar, billing card, and builder nudge. Includes eslint import-order auto-fixes in TopUpModal and LeadFormsContext. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): rename OBSERVE to MANAGE, add Credits & Billing link and Hire-an-Expert footer button Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): extract DograhCreditsCard with top-up + hire CTAs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): add Credits & Billing page Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(lead-gen): move Dograh Model Credits card out of Agent Runs to /billing Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): delayed Hire-an-Expert nudge on the workflow builder Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(ui): add lint:lead-flow guard script Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ui): restructure lead forms, self-serve Buy Credits, dialog blur Revised lead-capture surfaces and credits bar: - Dialog overlay gains backdrop blur (bg-black/60 backdrop-blur-sm). - Shared primitives: LeadModalShell (icon/eyebrow header, scrollable body, sticky footer, trust-line slot), PhoneField (react-international-phone, dark, E.164 out), FormTrustLine ("Average response: under 10 minutes..."). - HireExpertModal: Name, Company, Job title, agent goal, Phone (required), monthly volume. EnterpriseModal: + work email (required logged-out), conditional deployment (yes/no/maybe, source-gated), agent goal. OnboardingModal: drop useCase. Phone mandatory except onboarding. - Volume buckets match the backend qualifier (0-5k/5k-100k/100k+/not-sure). - Delete TopUpModal; DograhCreditsCard now self-serve Buy Credits (amount chips $5/$10/$25/$50/$100 + custom min $5 → startTopUp seam) + Hire an Expert + dashed custom-pricing link opening Enterprise (billing_custom_pricing). - PostHog events: drop topup_*, add buy_credits_clicked, buy_credits_amount_selected, custom_pricing_clicked. LeadFormsContext drops topup; LeadKind/LeadSource updated. - Introduce a single --cta warm accent token (CTAs + focus rings only). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ui): split-screen auth + enterprise CTA + dark theme default - AuthShell: dark two-column auth layout (brand/value panel with CSS-only waveform motif + proof points + Bland-style enterprise CTA block on the left, zinc-900 form card on the right; single-column on mobile). - AuthEnterpriseCTA: "Talk to our team" → dograh.com/contact?intent=enterprise. - stack-theme: dark StackTheme token overrides synced to globals.css. - page.tsx: wrap StackHandler (non-fullPage) in AuthShell + StackTheme; local-auth fallback preserved inside the shell. BackButton slimmed for the card. - Dark locked as default: <html className="dark">, next-themes ThemeProvider (defaultTheme="dark", enableSystem=false); inline no-FOUC script defaults dark. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ui rezig, onboarding, billing, hire us & on prem cues * ui changes * chore: update comment * chore: untrack docs/superpowers and gitignore it * feat: refactor user configuration table * feat(ui): 'check your email' confirmation on lead forms Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * added email and country in form submissions * chore: update leads api * fix: wrap dograh model config in card --------- Co-authored-by: Pritesh <pritesh@dograh.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:49:33 +05:30
from api.enums import UserConfigurationKey
from api.schemas.ai_model_configuration import EffectiveAIModelConfiguration
2025-09-09 14:37:32 +05:30
class UserClient(BaseDBClient):
async def get_or_create_user_by_provider_id(
self, provider_id: str
) -> tuple[UserModel, bool]:
"""Return (user, was_created) tuple."""
2025-09-09 14:37:32 +05:30
async with self.async_session() as session:
# First try to get existing user
result = await session.execute(
select(UserModel).where(UserModel.provider_id == provider_id)
)
user = result.scalars().first()
if user is not None:
return user, False
# Use PostgreSQL's INSERT ... ON CONFLICT DO NOTHING
# This is atomic and handles race conditions at the database level
from sqlalchemy.dialects.postgresql import insert
2025-09-09 14:37:32 +05:30
stmt = insert(UserModel.__table__).values(
provider_id=provider_id,
created_at=datetime.now(timezone.utc),
selected_organization_id=None, # Will be set later
is_superuser=False, # Default value
)
# ON CONFLICT DO NOTHING - if another request already inserted, this becomes a no-op
stmt = stmt.on_conflict_do_nothing(index_elements=["provider_id"])
result = await session.execute(stmt)
await session.commit()
was_created = result.rowcount > 0
2025-09-09 14:37:32 +05:30
# Now fetch the user (either the one we just created or the one that existed)
result = await session.execute(
select(UserModel).where(UserModel.provider_id == provider_id)
)
user = result.scalars().first()
if user is None:
# This should never happen, but handle it just in case
error_msg = (
f"Failed to create or fetch user with provider_id {provider_id}"
2025-09-09 14:37:32 +05:30
)
raise ValueError(error_msg)
return user, was_created
2025-09-09 14:37:32 +05:30
async def get_user_by_id(self, user_id: int) -> UserModel | None:
"""Fetch a user by their internal ID."""
async with self.async_session() as session:
result = await session.execute(
select(UserModel).where(UserModel.id == user_id)
)
return result.scalars().first()
feat: UI refresh and user onboarding (#430) * docs: design spec for lead-gen surfaces (Credits & Billing, Hire-an-Expert, Top-up, Enterprise) Add brainstorming spec for: sidebar OBSERVE→MANAGE rename + Credits & Billing link + Hire-an-Expert footer button; new /billing page with extracted Dograh Model Credits card + CTAs; Top-up / Hire-an-Expert / Enterprise intake modals with inline math captcha; and a workflow-builder Hire-an-Expert nudge. Frontend only; submissions fire PostHog events via a submitLead() seam for a future MongoDB endpoint. Also gitignore .superpowers/ brainstorm mockups. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: implementation plan for user-onboarding lead-gen surfaces 14 bite-sized tasks: PostHog events, shared helpers (field options, work-email blocklist, submitLead seam, math captcha), three intake modals (enterprise/hire/top-up), LeadFormsProvider context, AppLayout mount, sidebar MANAGE rename + Credits & Billing link + footer Hire button, extracted DograhCreditsCard, /billing page, credits removal from Agent Runs, builder nudge, and a full verification/dogfood pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): register PostHog events for lead-gen surfaces Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): shared field options, work-email validation, and submit seam Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): inline math captcha field Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): enterprise intake modal Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): hire-an-expert modal with enterprise link Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): top-up modal with >20k volume-pricing gate Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): shared lead-forms context provider Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): mount LeadFormsProvider in app layout Wrap the sidebar branch of AppLayout with LeadFormsProvider so the shared lead modals are available to the sidebar, billing card, and builder nudge. Includes eslint import-order auto-fixes in TopUpModal and LeadFormsContext. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): rename OBSERVE to MANAGE, add Credits & Billing link and Hire-an-Expert footer button Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): extract DograhCreditsCard with top-up + hire CTAs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): add Credits & Billing page Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(lead-gen): move Dograh Model Credits card out of Agent Runs to /billing Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): delayed Hire-an-Expert nudge on the workflow builder Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(ui): add lint:lead-flow guard script Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ui): restructure lead forms, self-serve Buy Credits, dialog blur Revised lead-capture surfaces and credits bar: - Dialog overlay gains backdrop blur (bg-black/60 backdrop-blur-sm). - Shared primitives: LeadModalShell (icon/eyebrow header, scrollable body, sticky footer, trust-line slot), PhoneField (react-international-phone, dark, E.164 out), FormTrustLine ("Average response: under 10 minutes..."). - HireExpertModal: Name, Company, Job title, agent goal, Phone (required), monthly volume. EnterpriseModal: + work email (required logged-out), conditional deployment (yes/no/maybe, source-gated), agent goal. OnboardingModal: drop useCase. Phone mandatory except onboarding. - Volume buckets match the backend qualifier (0-5k/5k-100k/100k+/not-sure). - Delete TopUpModal; DograhCreditsCard now self-serve Buy Credits (amount chips $5/$10/$25/$50/$100 + custom min $5 → startTopUp seam) + Hire an Expert + dashed custom-pricing link opening Enterprise (billing_custom_pricing). - PostHog events: drop topup_*, add buy_credits_clicked, buy_credits_amount_selected, custom_pricing_clicked. LeadFormsContext drops topup; LeadKind/LeadSource updated. - Introduce a single --cta warm accent token (CTAs + focus rings only). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ui): split-screen auth + enterprise CTA + dark theme default - AuthShell: dark two-column auth layout (brand/value panel with CSS-only waveform motif + proof points + Bland-style enterprise CTA block on the left, zinc-900 form card on the right; single-column on mobile). - AuthEnterpriseCTA: "Talk to our team" → dograh.com/contact?intent=enterprise. - stack-theme: dark StackTheme token overrides synced to globals.css. - page.tsx: wrap StackHandler (non-fullPage) in AuthShell + StackTheme; local-auth fallback preserved inside the shell. BackButton slimmed for the card. - Dark locked as default: <html className="dark">, next-themes ThemeProvider (defaultTheme="dark", enableSystem=false); inline no-FOUC script defaults dark. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ui rezig, onboarding, billing, hire us & on prem cues * ui changes * chore: update comment * chore: untrack docs/superpowers and gitignore it * feat: refactor user configuration table * feat(ui): 'check your email' confirmation on lead forms Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * added email and country in form submissions * chore: update leads api * fix: wrap dograh model config in card --------- Co-authored-by: Pritesh <pritesh@dograh.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:49:33 +05:30
async def _get_user_configuration_row(
self, session, user_id: int, key: str
) -> UserConfigurationModel | None:
result = await session.execute(
select(UserConfigurationModel).where(
UserConfigurationModel.user_id == user_id,
UserConfigurationModel.key == key,
)
)
return result.scalars().first()
async def get_user_configuration_value(self, user_id: int, key: str) -> dict | None:
"""Get the JSON value stored for a user under `key`, or None."""
async with self.async_session() as session:
row = await self._get_user_configuration_row(session, user_id, key)
return row.configuration if row else None
async def upsert_user_configuration_value(
self, user_id: int, key: str, value: dict
) -> dict:
"""Create or update the JSON value stored for a user under `key`."""
async with self.async_session() as session:
row = await self._get_user_configuration_row(session, user_id, key)
if row:
row.configuration = value
else:
row = UserConfigurationModel(
user_id=user_id, key=key, configuration=value
)
session.add(row)
try:
await session.commit()
except Exception as e:
await session.rollback()
raise e
await session.refresh(row)
return row.configuration
async def get_user_configurations(
self, user_id: int
) -> EffectiveAIModelConfiguration:
2025-09-09 14:37:32 +05:30
async with self.async_session() as session:
feat: UI refresh and user onboarding (#430) * docs: design spec for lead-gen surfaces (Credits & Billing, Hire-an-Expert, Top-up, Enterprise) Add brainstorming spec for: sidebar OBSERVE→MANAGE rename + Credits & Billing link + Hire-an-Expert footer button; new /billing page with extracted Dograh Model Credits card + CTAs; Top-up / Hire-an-Expert / Enterprise intake modals with inline math captcha; and a workflow-builder Hire-an-Expert nudge. Frontend only; submissions fire PostHog events via a submitLead() seam for a future MongoDB endpoint. Also gitignore .superpowers/ brainstorm mockups. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: implementation plan for user-onboarding lead-gen surfaces 14 bite-sized tasks: PostHog events, shared helpers (field options, work-email blocklist, submitLead seam, math captcha), three intake modals (enterprise/hire/top-up), LeadFormsProvider context, AppLayout mount, sidebar MANAGE rename + Credits & Billing link + footer Hire button, extracted DograhCreditsCard, /billing page, credits removal from Agent Runs, builder nudge, and a full verification/dogfood pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): register PostHog events for lead-gen surfaces Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): shared field options, work-email validation, and submit seam Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): inline math captcha field Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): enterprise intake modal Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): hire-an-expert modal with enterprise link Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): top-up modal with >20k volume-pricing gate Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): shared lead-forms context provider Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): mount LeadFormsProvider in app layout Wrap the sidebar branch of AppLayout with LeadFormsProvider so the shared lead modals are available to the sidebar, billing card, and builder nudge. Includes eslint import-order auto-fixes in TopUpModal and LeadFormsContext. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): rename OBSERVE to MANAGE, add Credits & Billing link and Hire-an-Expert footer button Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): extract DograhCreditsCard with top-up + hire CTAs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): add Credits & Billing page Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(lead-gen): move Dograh Model Credits card out of Agent Runs to /billing Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): delayed Hire-an-Expert nudge on the workflow builder Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(ui): add lint:lead-flow guard script Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ui): restructure lead forms, self-serve Buy Credits, dialog blur Revised lead-capture surfaces and credits bar: - Dialog overlay gains backdrop blur (bg-black/60 backdrop-blur-sm). - Shared primitives: LeadModalShell (icon/eyebrow header, scrollable body, sticky footer, trust-line slot), PhoneField (react-international-phone, dark, E.164 out), FormTrustLine ("Average response: under 10 minutes..."). - HireExpertModal: Name, Company, Job title, agent goal, Phone (required), monthly volume. EnterpriseModal: + work email (required logged-out), conditional deployment (yes/no/maybe, source-gated), agent goal. OnboardingModal: drop useCase. Phone mandatory except onboarding. - Volume buckets match the backend qualifier (0-5k/5k-100k/100k+/not-sure). - Delete TopUpModal; DograhCreditsCard now self-serve Buy Credits (amount chips $5/$10/$25/$50/$100 + custom min $5 → startTopUp seam) + Hire an Expert + dashed custom-pricing link opening Enterprise (billing_custom_pricing). - PostHog events: drop topup_*, add buy_credits_clicked, buy_credits_amount_selected, custom_pricing_clicked. LeadFormsContext drops topup; LeadKind/LeadSource updated. - Introduce a single --cta warm accent token (CTAs + focus rings only). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ui): split-screen auth + enterprise CTA + dark theme default - AuthShell: dark two-column auth layout (brand/value panel with CSS-only waveform motif + proof points + Bland-style enterprise CTA block on the left, zinc-900 form card on the right; single-column on mobile). - AuthEnterpriseCTA: "Talk to our team" → dograh.com/contact?intent=enterprise. - stack-theme: dark StackTheme token overrides synced to globals.css. - page.tsx: wrap StackHandler (non-fullPage) in AuthShell + StackTheme; local-auth fallback preserved inside the shell. BackButton slimmed for the card. - Dark locked as default: <html className="dark">, next-themes ThemeProvider (defaultTheme="dark", enableSystem=false); inline no-FOUC script defaults dark. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ui rezig, onboarding, billing, hire us & on prem cues * ui changes * chore: update comment * chore: untrack docs/superpowers and gitignore it * feat: refactor user configuration table * feat(ui): 'check your email' confirmation on lead forms Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * added email and country in form submissions * chore: update leads api * fix: wrap dograh model config in card --------- Co-authored-by: Pritesh <pritesh@dograh.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:49:33 +05:30
configuration_obj = await self._get_user_configuration_row(
session, user_id, UserConfigurationKey.MODEL_CONFIGURATION.value
2025-09-09 14:37:32 +05:30
)
if not configuration_obj:
return EffectiveAIModelConfiguration()
2025-09-09 14:37:32 +05:30
try:
return EffectiveAIModelConfiguration.model_validate(
{
**configuration_obj.configuration,
"last_validated_at": configuration_obj.last_validated_at,
}
)
except ValidationError as e:
# If configuration contains an unsupported provider,
# return a default configuration without failing
logger.warning(
f"Failed to validate user configuration for user {user_id}: {e}. "
"Returning default configuration."
)
return EffectiveAIModelConfiguration()
2025-09-09 14:37:32 +05:30
async def update_user_configuration(
self, user_id: int, configuration: EffectiveAIModelConfiguration
) -> EffectiveAIModelConfiguration:
feat: UI refresh and user onboarding (#430) * docs: design spec for lead-gen surfaces (Credits & Billing, Hire-an-Expert, Top-up, Enterprise) Add brainstorming spec for: sidebar OBSERVE→MANAGE rename + Credits & Billing link + Hire-an-Expert footer button; new /billing page with extracted Dograh Model Credits card + CTAs; Top-up / Hire-an-Expert / Enterprise intake modals with inline math captcha; and a workflow-builder Hire-an-Expert nudge. Frontend only; submissions fire PostHog events via a submitLead() seam for a future MongoDB endpoint. Also gitignore .superpowers/ brainstorm mockups. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: implementation plan for user-onboarding lead-gen surfaces 14 bite-sized tasks: PostHog events, shared helpers (field options, work-email blocklist, submitLead seam, math captcha), three intake modals (enterprise/hire/top-up), LeadFormsProvider context, AppLayout mount, sidebar MANAGE rename + Credits & Billing link + footer Hire button, extracted DograhCreditsCard, /billing page, credits removal from Agent Runs, builder nudge, and a full verification/dogfood pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): register PostHog events for lead-gen surfaces Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): shared field options, work-email validation, and submit seam Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): inline math captcha field Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): enterprise intake modal Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): hire-an-expert modal with enterprise link Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): top-up modal with >20k volume-pricing gate Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): shared lead-forms context provider Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): mount LeadFormsProvider in app layout Wrap the sidebar branch of AppLayout with LeadFormsProvider so the shared lead modals are available to the sidebar, billing card, and builder nudge. Includes eslint import-order auto-fixes in TopUpModal and LeadFormsContext. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): rename OBSERVE to MANAGE, add Credits & Billing link and Hire-an-Expert footer button Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): extract DograhCreditsCard with top-up + hire CTAs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): add Credits & Billing page Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(lead-gen): move Dograh Model Credits card out of Agent Runs to /billing Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): delayed Hire-an-Expert nudge on the workflow builder Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(ui): add lint:lead-flow guard script Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ui): restructure lead forms, self-serve Buy Credits, dialog blur Revised lead-capture surfaces and credits bar: - Dialog overlay gains backdrop blur (bg-black/60 backdrop-blur-sm). - Shared primitives: LeadModalShell (icon/eyebrow header, scrollable body, sticky footer, trust-line slot), PhoneField (react-international-phone, dark, E.164 out), FormTrustLine ("Average response: under 10 minutes..."). - HireExpertModal: Name, Company, Job title, agent goal, Phone (required), monthly volume. EnterpriseModal: + work email (required logged-out), conditional deployment (yes/no/maybe, source-gated), agent goal. OnboardingModal: drop useCase. Phone mandatory except onboarding. - Volume buckets match the backend qualifier (0-5k/5k-100k/100k+/not-sure). - Delete TopUpModal; DograhCreditsCard now self-serve Buy Credits (amount chips $5/$10/$25/$50/$100 + custom min $5 → startTopUp seam) + Hire an Expert + dashed custom-pricing link opening Enterprise (billing_custom_pricing). - PostHog events: drop topup_*, add buy_credits_clicked, buy_credits_amount_selected, custom_pricing_clicked. LeadFormsContext drops topup; LeadKind/LeadSource updated. - Introduce a single --cta warm accent token (CTAs + focus rings only). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ui): split-screen auth + enterprise CTA + dark theme default - AuthShell: dark two-column auth layout (brand/value panel with CSS-only waveform motif + proof points + Bland-style enterprise CTA block on the left, zinc-900 form card on the right; single-column on mobile). - AuthEnterpriseCTA: "Talk to our team" → dograh.com/contact?intent=enterprise. - stack-theme: dark StackTheme token overrides synced to globals.css. - page.tsx: wrap StackHandler (non-fullPage) in AuthShell + StackTheme; local-auth fallback preserved inside the shell. BackButton slimmed for the card. - Dark locked as default: <html className="dark">, next-themes ThemeProvider (defaultTheme="dark", enableSystem=false); inline no-FOUC script defaults dark. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ui rezig, onboarding, billing, hire us & on prem cues * ui changes * chore: update comment * chore: untrack docs/superpowers and gitignore it * feat: refactor user configuration table * feat(ui): 'check your email' confirmation on lead forms Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * added email and country in form submissions * chore: update leads api * fix: wrap dograh model config in card --------- Co-authored-by: Pritesh <pritesh@dograh.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:49:33 +05:30
value = await self.upsert_user_configuration_value(
user_id,
UserConfigurationKey.MODEL_CONFIGURATION.value,
configuration.model_dump(),
)
feat: UI refresh and user onboarding (#430) * docs: design spec for lead-gen surfaces (Credits & Billing, Hire-an-Expert, Top-up, Enterprise) Add brainstorming spec for: sidebar OBSERVE→MANAGE rename + Credits & Billing link + Hire-an-Expert footer button; new /billing page with extracted Dograh Model Credits card + CTAs; Top-up / Hire-an-Expert / Enterprise intake modals with inline math captcha; and a workflow-builder Hire-an-Expert nudge. Frontend only; submissions fire PostHog events via a submitLead() seam for a future MongoDB endpoint. Also gitignore .superpowers/ brainstorm mockups. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: implementation plan for user-onboarding lead-gen surfaces 14 bite-sized tasks: PostHog events, shared helpers (field options, work-email blocklist, submitLead seam, math captcha), three intake modals (enterprise/hire/top-up), LeadFormsProvider context, AppLayout mount, sidebar MANAGE rename + Credits & Billing link + footer Hire button, extracted DograhCreditsCard, /billing page, credits removal from Agent Runs, builder nudge, and a full verification/dogfood pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): register PostHog events for lead-gen surfaces Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): shared field options, work-email validation, and submit seam Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): inline math captcha field Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): enterprise intake modal Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): hire-an-expert modal with enterprise link Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): top-up modal with >20k volume-pricing gate Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): shared lead-forms context provider Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): mount LeadFormsProvider in app layout Wrap the sidebar branch of AppLayout with LeadFormsProvider so the shared lead modals are available to the sidebar, billing card, and builder nudge. Includes eslint import-order auto-fixes in TopUpModal and LeadFormsContext. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): rename OBSERVE to MANAGE, add Credits & Billing link and Hire-an-Expert footer button Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): extract DograhCreditsCard with top-up + hire CTAs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): add Credits & Billing page Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(lead-gen): move Dograh Model Credits card out of Agent Runs to /billing Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): delayed Hire-an-Expert nudge on the workflow builder Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(ui): add lint:lead-flow guard script Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ui): restructure lead forms, self-serve Buy Credits, dialog blur Revised lead-capture surfaces and credits bar: - Dialog overlay gains backdrop blur (bg-black/60 backdrop-blur-sm). - Shared primitives: LeadModalShell (icon/eyebrow header, scrollable body, sticky footer, trust-line slot), PhoneField (react-international-phone, dark, E.164 out), FormTrustLine ("Average response: under 10 minutes..."). - HireExpertModal: Name, Company, Job title, agent goal, Phone (required), monthly volume. EnterpriseModal: + work email (required logged-out), conditional deployment (yes/no/maybe, source-gated), agent goal. OnboardingModal: drop useCase. Phone mandatory except onboarding. - Volume buckets match the backend qualifier (0-5k/5k-100k/100k+/not-sure). - Delete TopUpModal; DograhCreditsCard now self-serve Buy Credits (amount chips $5/$10/$25/$50/$100 + custom min $5 → startTopUp seam) + Hire an Expert + dashed custom-pricing link opening Enterprise (billing_custom_pricing). - PostHog events: drop topup_*, add buy_credits_clicked, buy_credits_amount_selected, custom_pricing_clicked. LeadFormsContext drops topup; LeadKind/LeadSource updated. - Introduce a single --cta warm accent token (CTAs + focus rings only). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ui): split-screen auth + enterprise CTA + dark theme default - AuthShell: dark two-column auth layout (brand/value panel with CSS-only waveform motif + proof points + Bland-style enterprise CTA block on the left, zinc-900 form card on the right; single-column on mobile). - AuthEnterpriseCTA: "Talk to our team" → dograh.com/contact?intent=enterprise. - stack-theme: dark StackTheme token overrides synced to globals.css. - page.tsx: wrap StackHandler (non-fullPage) in AuthShell + StackTheme; local-auth fallback preserved inside the shell. BackButton slimmed for the card. - Dark locked as default: <html className="dark">, next-themes ThemeProvider (defaultTheme="dark", enableSystem=false); inline no-FOUC script defaults dark. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ui rezig, onboarding, billing, hire us & on prem cues * ui changes * chore: update comment * chore: untrack docs/superpowers and gitignore it * feat: refactor user configuration table * feat(ui): 'check your email' confirmation on lead forms Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * added email and country in form submissions * chore: update leads api * fix: wrap dograh model config in card --------- Co-authored-by: Pritesh <pritesh@dograh.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:49:33 +05:30
return EffectiveAIModelConfiguration.model_validate(value)
2025-09-09 14:37:32 +05:30
async def update_user_configuration_last_validated_at(self, user_id: int) -> None:
async with self.async_session() as session:
feat: UI refresh and user onboarding (#430) * docs: design spec for lead-gen surfaces (Credits & Billing, Hire-an-Expert, Top-up, Enterprise) Add brainstorming spec for: sidebar OBSERVE→MANAGE rename + Credits & Billing link + Hire-an-Expert footer button; new /billing page with extracted Dograh Model Credits card + CTAs; Top-up / Hire-an-Expert / Enterprise intake modals with inline math captcha; and a workflow-builder Hire-an-Expert nudge. Frontend only; submissions fire PostHog events via a submitLead() seam for a future MongoDB endpoint. Also gitignore .superpowers/ brainstorm mockups. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: implementation plan for user-onboarding lead-gen surfaces 14 bite-sized tasks: PostHog events, shared helpers (field options, work-email blocklist, submitLead seam, math captcha), three intake modals (enterprise/hire/top-up), LeadFormsProvider context, AppLayout mount, sidebar MANAGE rename + Credits & Billing link + footer Hire button, extracted DograhCreditsCard, /billing page, credits removal from Agent Runs, builder nudge, and a full verification/dogfood pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): register PostHog events for lead-gen surfaces Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): shared field options, work-email validation, and submit seam Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): inline math captcha field Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): enterprise intake modal Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): hire-an-expert modal with enterprise link Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): top-up modal with >20k volume-pricing gate Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): shared lead-forms context provider Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): mount LeadFormsProvider in app layout Wrap the sidebar branch of AppLayout with LeadFormsProvider so the shared lead modals are available to the sidebar, billing card, and builder nudge. Includes eslint import-order auto-fixes in TopUpModal and LeadFormsContext. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): rename OBSERVE to MANAGE, add Credits & Billing link and Hire-an-Expert footer button Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): extract DograhCreditsCard with top-up + hire CTAs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): add Credits & Billing page Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(lead-gen): move Dograh Model Credits card out of Agent Runs to /billing Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(lead-gen): delayed Hire-an-Expert nudge on the workflow builder Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(ui): add lint:lead-flow guard script Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ui): restructure lead forms, self-serve Buy Credits, dialog blur Revised lead-capture surfaces and credits bar: - Dialog overlay gains backdrop blur (bg-black/60 backdrop-blur-sm). - Shared primitives: LeadModalShell (icon/eyebrow header, scrollable body, sticky footer, trust-line slot), PhoneField (react-international-phone, dark, E.164 out), FormTrustLine ("Average response: under 10 minutes..."). - HireExpertModal: Name, Company, Job title, agent goal, Phone (required), monthly volume. EnterpriseModal: + work email (required logged-out), conditional deployment (yes/no/maybe, source-gated), agent goal. OnboardingModal: drop useCase. Phone mandatory except onboarding. - Volume buckets match the backend qualifier (0-5k/5k-100k/100k+/not-sure). - Delete TopUpModal; DograhCreditsCard now self-serve Buy Credits (amount chips $5/$10/$25/$50/$100 + custom min $5 → startTopUp seam) + Hire an Expert + dashed custom-pricing link opening Enterprise (billing_custom_pricing). - PostHog events: drop topup_*, add buy_credits_clicked, buy_credits_amount_selected, custom_pricing_clicked. LeadFormsContext drops topup; LeadKind/LeadSource updated. - Introduce a single --cta warm accent token (CTAs + focus rings only). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(ui): split-screen auth + enterprise CTA + dark theme default - AuthShell: dark two-column auth layout (brand/value panel with CSS-only waveform motif + proof points + Bland-style enterprise CTA block on the left, zinc-900 form card on the right; single-column on mobile). - AuthEnterpriseCTA: "Talk to our team" → dograh.com/contact?intent=enterprise. - stack-theme: dark StackTheme token overrides synced to globals.css. - page.tsx: wrap StackHandler (non-fullPage) in AuthShell + StackTheme; local-auth fallback preserved inside the shell. BackButton slimmed for the card. - Dark locked as default: <html className="dark">, next-themes ThemeProvider (defaultTheme="dark", enableSystem=false); inline no-FOUC script defaults dark. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ui rezig, onboarding, billing, hire us & on prem cues * ui changes * chore: update comment * chore: untrack docs/superpowers and gitignore it * feat: refactor user configuration table * feat(ui): 'check your email' confirmation on lead forms Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * added email and country in form submissions * chore: update leads api * fix: wrap dograh model config in card --------- Co-authored-by: Pritesh <pritesh@dograh.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:49:33 +05:30
configuration_obj = await self._get_user_configuration_row(
session, user_id, UserConfigurationKey.MODEL_CONFIGURATION.value
2025-09-09 14:37:32 +05:30
)
if not configuration_obj:
raise ValueError(f"User configuration with ID {user_id} not found")
configuration_obj.last_validated_at = datetime.now()
try:
await session.commit()
except Exception as e:
await session.rollback()
raise e
await session.refresh(configuration_obj)
async def update_user_selected_organization(
self, user_id: int, organization_id: int
) -> None:
"""Update the user's selected organization ID."""
async with self.async_session() as session:
from sqlalchemy import update
# Use a direct UPDATE statement to avoid race conditions
# This is atomic at the database level
stmt = (
update(UserModel)
.where(UserModel.id == user_id)
.values(selected_organization_id=organization_id)
)
result = await session.execute(stmt)
if result.rowcount == 0:
raise ValueError(f"User with ID {user_id} not found")
await session.commit()
async def update_user_email(self, user_id: int, email: str) -> None:
"""Update the user's email address."""
async with self.async_session() as session:
from sqlalchemy import update
stmt = (
update(UserModel)
.where(UserModel.id == user_id)
.values(email=email.lower())
)
await session.execute(stmt)
await session.commit()
async def get_user_by_email(self, email: str) -> UserModel | None:
"""Fetch a user by their email address (case-insensitive).
Email addresses are case-insensitive in practice, so a user who
signed up as "User@example.com" must still be found when they later
log in as "user@example.com". Compare on lower(email) so lookups are
robust to capitalization differences across sign-in flows.
"""
normalized_email = email.lower()
async with self.async_session() as session:
result = await session.execute(
select(UserModel).where(func.lower(UserModel.email) == normalized_email)
)
return result.scalars().first()
async def create_user_with_email(
self, email: str, password_hash: str, name: str | None = None
) -> UserModel:
"""Create a new user with email and password hash."""
async with self.async_session() as session:
user = UserModel(
provider_id=f"oss_{int(datetime.now(timezone.utc).timestamp())}_{uuid.uuid4()}",
email=email.lower(),
password_hash=password_hash,
)
session.add(user)
await session.commit()
await session.refresh(user)
return user