Documentation
¶
Overview ¶
Package handler — audit_log.go.
See docs/api/handlers/audit-log.md for the filter shape, cursor pagination semantics, and ACL boundary discussion.
Package handler — billing.go ¶
BillingHandler serves the read-only usage-transparency endpoint GET /businesses/{id}/billing/summary. It exposes plan + credits + usage; no payment surface (that is Track-B). Gated by authz.PermBillingRead.
Package handler — business_deletion.go
DELETE /api/v1/businesses/{id} and POST /api/v1/businesses/{id}/restore. Both routes sit inside the /businesses/{id} group (RequireBusinessAccess); the OWNER-role gate lives in the service layer and surfaces as 403. The delete endpoint is idempotent — a second call returns 423 from the service.
Package handler — channel_request.go.
ChannelRequestHandler implements the not-yet-supported-channel fake-door: POST /businesses/{id}/channel-requests records demand for a channel OneVoice does not support yet, and GET returns the per-channel demand aggregate for the business. Both are business-scoped (RequireBusinessAccess + content perm) so a caller only ever writes or reads its own tenant's signals.
Package handler — consents.go ¶
Three endpoints:
- POST /auth/consents — submit the re-consent modal (Surface E). 409 version_mismatch when the build's currentVersion has bumped since the modal was rendered; 400 consent_required on missing slugs; 204 on success. - GET /users/me/consents — Surface F (Withdraw / status list). - POST /users/me/consents/pdn/withdraw — Surface F danger CTA. Triggers the deletion flow; 423 when the account is already mid-deletion.
All three routes JWT-required + always-reachable (precedent): they are NOT wrapped by RequireVerifiedEmail* or BlockWritesDuringGrace because the right-to-erasure / right-to-withdraw cannot be gated by other middleware (152-ФЗ Art. 21).
Package handler — feedback.go.
FeedbackHandler implements POST /feedback — in-app user feedback capture. JWT-required, business-agnostic (the widget is reachable everywhere, including before an organization exists).
Package handler — HITL resolve + resume + GET /tools endpoints. See docs/api/handlers/hitl.md.
Package handler — invitations.go.
See docs/api/handlers/invitations.md for the invitation lifecycle, ACL boundaries, and the race-safe single-use guarantee on accept.
Package handler — landing.go.
LandingHandler serves public waitlist signups, channel votes, and CTA events. Requests are rate-limited per IP and successful captures return 204.
Package handler — user_deletion.go
DELETE /api/v1/users/me and POST /api/v1/users/me/restore. Both routes are JWT-required but NOT wrapped by RequireVerifiedEmail* (right to erasure cannot be gated by verification) and NOT wrapped by BlockWritesDuringGrace (the restore endpoint is the explicit escape hatch from the grace state; the delete endpoint is idempotent — second call returns 423 from the service layer, not from middleware).
Index ¶
- Constants
- Variables
- func EmailVerificationErrorStatus(code string) int
- type AccountDeletionInfo
- type AccountDeletionServiceAPI
- type AgentTaskHandler
- func (h *AgentTaskHandler) DismissTask(w http.ResponseWriter, r *http.Request)
- func (h *AgentTaskHandler) ListTasks(w http.ResponseWriter, r *http.Request)
- func (h *AgentTaskHandler) RerunVerification(w http.ResponseWriter, r *http.Request)
- func (h *AgentTaskHandler) RetryTask(w http.ResponseWriter, r *http.Request)
- func (h *AgentTaskHandler) StreamTasks(w http.ResponseWriter, r *http.Request)
- type AgentTaskService
- type AuditLogDTO
- type AuditLogHandler
- type AuditLogListResponse
- type AuditLogLister
- type AuthHandler
- func (h *AuthHandler) ChangePassword(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) ConfirmPasswordReset(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) EmailBeforeVerify(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) Me(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) RequestPasswordReset(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) SetConsentDiffer(d ConsentDiffer)
- func (h *AuthHandler) SetEmailVerificationService(s EmailVerificationServiceAPI)
- func (h *AuthHandler) SetMeUserExtraGetter(fn func(ctx context.Context, userID uuid.UUID) (*domain.User, error))
- func (h *AuthHandler) SetPasswordResetService(s PasswordResetServiceAPI)
- func (h *AuthHandler) UpdatePreferredLocale(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) UpdateProfile(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) VerifyConfirm(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) VerifyResend(w http.ResponseWriter, r *http.Request)
- func (h *AuthHandler) WithInviteOnlyRegistration(gate RegistrationAccessGate) *AuthHandler
- func (h *AuthHandler) WithLockout(lock *lockout.Lockout, captcha service.SmartCaptchaVerifier, failOpen bool) *AuthHandler
- type BillingHandler
- type BillingService
- type BillingSummarizer
- type BusinessDeletionHandler
- type BusinessDeletionServiceAPI
- type BusinessHandler
- func (h *BusinessHandler) CreateBusiness(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) GetBusiness(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) GetBusinessToolApprovals(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) GetDescriptionTemplate(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) GetDriftAlerts(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) GetOwnerBrief(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) GetReviewAutopilot(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) GetVoiceProfile(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) ListUserBusinesses(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) SetToolsCache(c ToolsCache)
- func (h *BusinessHandler) UpdateBusiness(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) UpdateBusinessToolApprovals(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) UpdateDescriptionTemplate(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) UpdateDriftAlerts(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) UpdateOwnerBrief(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) UpdateReviewAutopilot(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) UpdateSchedule(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) UpdateVoiceProfile(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) UpdateVoiceTone(w http.ResponseWriter, r *http.Request)
- func (h *BusinessHandler) UploadLogo(w http.ResponseWriter, r *http.Request)
- type BusinessService
- type BusinessSyncer
- type ChannelRequestHandler
- type ChatProxyHandler
- type ConsentDiffer
- type ConsentsHandler
- type ConsentsListerAPI
- type ConsentsServiceAPI
- type ContentTemplateHandler
- func (h *ContentTemplateHandler) Create(w http.ResponseWriter, r *http.Request)
- func (h *ContentTemplateHandler) CreateFromPost(w http.ResponseWriter, r *http.Request)
- func (h *ContentTemplateHandler) CreateFromReview(w http.ResponseWriter, r *http.Request)
- func (h *ContentTemplateHandler) Delete(w http.ResponseWriter, r *http.Request)
- func (h *ContentTemplateHandler) Get(w http.ResponseWriter, r *http.Request)
- func (h *ContentTemplateHandler) List(w http.ResponseWriter, r *http.Request)
- func (h *ContentTemplateHandler) Render(w http.ResponseWriter, r *http.Request)
- func (h *ContentTemplateHandler) Update(w http.ResponseWriter, r *http.Request)
- type ConversationHandler
- func (h *ConversationHandler) CreateConversation(w http.ResponseWriter, r *http.Request)
- func (h *ConversationHandler) DeleteConversation(w http.ResponseWriter, r *http.Request)
- func (h *ConversationHandler) GetConversation(w http.ResponseWriter, r *http.Request)
- func (h *ConversationHandler) ListConversations(w http.ResponseWriter, r *http.Request)
- func (h *ConversationHandler) ListMessages(w http.ResponseWriter, r *http.Request)
- func (h *ConversationHandler) MoveConversation(w http.ResponseWriter, r *http.Request)
- func (h *ConversationHandler) Pin(w http.ResponseWriter, r *http.Request)
- func (h *ConversationHandler) Unpin(w http.ResponseWriter, r *http.Request)
- func (h *ConversationHandler) UpdateConversation(w http.ResponseWriter, r *http.Request)
- type ConversationService
- type EmailVerificationServiceAPI
- type ErrorResponse
- type FeedbackHandler
- type HITLHandler
- func (h *HITLHandler) GetTools(w http.ResponseWriter, r *http.Request)
- func (h *HITLHandler) ResolvePendingToolCalls(w http.ResponseWriter, r *http.Request)
- func (h *HITLHandler) Resume(w http.ResponseWriter, r *http.Request)
- func (h *HITLHandler) SetAuditLogger(auditLogger audit.Logger)
- func (h *HITLHandler) SetSSECounter(c *ssecounter.Counter, defaultTier string)
- type IntegrationHandler
- func (h *IntegrationHandler) DeleteIntegration(w http.ResponseWriter, r *http.Request)
- func (h *IntegrationHandler) GetIntegrationsDrift(w http.ResponseWriter, r *http.Request)
- func (h *IntegrationHandler) ListIntegrations(w http.ResponseWriter, r *http.Request)
- func (h *IntegrationHandler) SetHealthChecker(checker connHealthChecker)
- func (h *IntegrationHandler) SetReconciler(reconciler driftLister, syncTrigger businessSyncTrigger)
- func (h *IntegrationHandler) VerifyIntegrations(w http.ResponseWriter, r *http.Request)
- type IntegrationService
- type InternalBillingHandler
- type InternalTokenHandler
- type InternalYandexSharedHandler
- type InvitationsHandler
- func (h *InvitationsHandler) Accept(w http.ResponseWriter, r *http.Request)
- func (h *InvitationsHandler) Create(w http.ResponseWriter, r *http.Request)
- func (h *InvitationsHandler) ListPending(w http.ResponseWriter, r *http.Request)
- func (h *InvitationsHandler) Preview(w http.ResponseWriter, r *http.Request)
- func (h *InvitationsHandler) Revoke(w http.ResponseWriter, r *http.Request)
- type LandingHandler
- type LoginRequest
- type LoginResponse
- type MeResponse
- type MembersHandler
- type PasswordResetServiceAPI
- type PermissionsHandler
- type PlatformAvailability
- type PlatformsHandler
- type PostHandler
- type PostService
- type PresenceHealthHandler
- type PresenceHealthScorer
- type PresenceHealthSnapshotStore
- type ProjectHandler
- func (h *ProjectHandler) ConversationCount(w http.ResponseWriter, r *http.Request)
- func (h *ProjectHandler) Create(w http.ResponseWriter, r *http.Request)
- func (h *ProjectHandler) Delete(w http.ResponseWriter, r *http.Request)
- func (h *ProjectHandler) Get(w http.ResponseWriter, r *http.Request)
- func (h *ProjectHandler) List(w http.ResponseWriter, r *http.Request)
- func (h *ProjectHandler) SetToolsCache(c ToolsCache)
- func (h *ProjectHandler) Update(w http.ResponseWriter, r *http.Request)
- type ProjectService
- type RefreshTokenResponse
- type RegistrationAccessGate
- type ReviewHandler
- func (h *ReviewHandler) BatchDraftReviews(w http.ResponseWriter, r *http.Request)
- func (h *ReviewHandler) BulkApproveReviews(w http.ResponseWriter, r *http.Request)
- func (h *ReviewHandler) GetDelegationMetrics(w http.ResponseWriter, r *http.Request)
- func (h *ReviewHandler) GetReview(w http.ResponseWriter, r *http.Request)
- func (h *ReviewHandler) GetReviewSLA(w http.ResponseWriter, r *http.Request)
- func (h *ReviewHandler) ListReviews(w http.ResponseWriter, r *http.Request)
- func (h *ReviewHandler) RefreshReviews(w http.ResponseWriter, r *http.Request)
- func (h *ReviewHandler) ReplyToReview(w http.ResponseWriter, r *http.Request)
- func (h *ReviewHandler) RetryReviewReply(w http.ResponseWriter, r *http.Request)
- func (h *ReviewHandler) SetSSECounter(c *ssecounter.Counter, defaultTier string)
- type ReviewService
- type RolesHandler
- func (h *RolesHandler) Create(w http.ResponseWriter, r *http.Request)
- func (h *RolesHandler) Delete(w http.ResponseWriter, r *http.Request)
- func (h *RolesHandler) List(w http.ResponseWriter, r *http.Request)
- func (h *RolesHandler) MyPermissions(w http.ResponseWriter, r *http.Request)
- func (h *RolesHandler) Update(w http.ResponseWriter, r *http.Request)
- type SearchHandler
- type SoleOwnerEntry
- type TelemetryHandler
- type TitlerHandler
- type TokenService
- type ToolsCache
- type UserDeletionHandler
- type UserService
- type ValidationErrorResponse
- type WeeklyValueRecapHandler
Constants ¶
const ( DefaultTaskLimit = 20 MaxTaskLimit = 100 )
Constants for task pagination
const ( DefaultConversationLimit = 20 MaxConversationLimit = 100 )
Pagination + ObjectID-length constants. See docs/api/handlers/conversation.md.
const ( // Lockout / SmartCaptcha codes for /auth/login. ErrCodeAccountLocked = "account_locked" ErrCodeCaptchaRequired = "captcha_required" ErrCodeCaptchaInvalid = "captcha_invalid" // Internal-error codes for the auth handler family. ErrCodeRegistrationInviteRequired = "registration_invite_required" ErrCodeRegisterInternal = "register_internal" // Register, post-create ErrCodeAutoLoginFailed = "auto_login_failed" // Register, auto-login ErrCodeRefreshInternal = "refresh_internal" // RefreshToken ErrCodeLogoutInternal = "logout_internal" // Logout ErrCodeGetUserInternal = "get_user_internal" // Me ErrCodeChangePasswordInternal = "change_password_internal" // ChangePassword ErrCodeUpdateLocaleInternal = "update_locale_internal" // UpdatePreferredLocale ErrCodeUpdateProfileInternal = "update_profile_internal" // UpdateProfile )
Machine-readable error code constants. The frontend resolves each code to a Russian string via the i18n catalog (messages/ru.json under common.errors.*).
const ( DefaultPostLimit = 20 MaxPostLimit = 100 )
Constants for post pagination
const ( DefaultReviewLimit = 20 MaxReviewLimit = 100 )
Constants for review pagination
const ResumeBatchHeader = "X-Onevoice-Resume-Batch-Id"
ResumeBatchHeader is the explicit HITL resume header. Pinned to the handler package so router + tests can keep using handler.ResumeBatchHeader.
Variables ¶
var SoleOwnerExtractor func(err error) (rows []SoleOwnerEntry, ok bool)
SoleOwnerExtractor is a package-level hook the wire layer fills with a closure that runs errors.As against *service.ErrSoleOwnerBusinesses. Kept as a hook so this handler package doesn't import the service package's concrete error type (which would force the service package to re-import a handler-side definition — back-pointer that breaks the layering).
Functions ¶
func EmailVerificationErrorStatus ¶
EmailVerificationErrorStatus maps verification public codes to canonical HTTP status codes. Mirrors the frontend services/frontend/lib/error_mapping.ts COPY map (21-CROSS-PLAN-CONTRACTS §4) so the backend and frontend agree on the wire shape.
Exposed as a func (not a map literal) so the constants reference http.Status* directly — easier to grep + harder to type-mismatch.
Types ¶
type AccountDeletionInfo ¶
type AccountDeletionInfo = openapi.AccountDeletionInfo
AccountDeletionInfo carries the 30-day grace window timestamps for /auth/me.
type AccountDeletionServiceAPI ¶
type AccountDeletionServiceAPI interface {
RequestDeletion(ctx context.Context, userID uuid.UUID, password, clientIP, userAgent, reason string) error
CancelDeletion(ctx context.Context, userID uuid.UUID, clientIP, userAgent string) error
GetScheduledDeletionAt(ctx context.Context, userID uuid.UUID) (time.Time, error)
}
AccountDeletionServiceAPI is the slice of *service.AccountDeletionService the handler consumes. Declared as an interface here so handler tests can pass an in-memory double (mirrors PasswordResetServiceAPI and EmailVerificationServiceAPI).
RequestDeletion grew a trailing `reason` parameter so ConsentService.WithdrawPDN can pass "consent_withdrawn" to bypass the bcrypt check. The user_deletion.go Delete handler always passes "".
type AgentTaskHandler ¶
type AgentTaskHandler struct {
// contains filtered or unexported fields
}
AgentTaskHandler handles agent task-related HTTP requests
func NewAgentTaskHandler ¶
func NewAgentTaskHandler(agentTaskService AgentTaskService, hub *taskhub.Hub) (*AgentTaskHandler, error)
NewAgentTaskHandler creates a new agent task handler instance
func (*AgentTaskHandler) DismissTask ¶
func (h *AgentTaskHandler) DismissTask(w http.ResponseWriter, r *http.Request)
DismissTask removes a failed task from the active list without deleting its audit record. It does not repeat or reverse the external action.
func (*AgentTaskHandler) ListTasks ¶
func (h *AgentTaskHandler) ListTasks(w http.ResponseWriter, r *http.Request)
ListTasks handles GET /api/v1/tasks
func (*AgentTaskHandler) RerunVerification ¶
func (h *AgentTaskHandler) RerunVerification(w http.ResponseWriter, r *http.Request)
RerunVerification repeats only the readonly external readback. writeLimit bounds abuse even though content.read is sufficient authorization.
func (*AgentTaskHandler) RetryTask ¶
func (h *AgentTaskHandler) RetryTask(w http.ResponseWriter, r *http.Request)
RetryTask handles POST /api/v1/tasks/{taskId}/retry — re-dispatches a RETRYABLE failed task to its platform agent without the LLM. The original tool call was already HITL-approved before it failed at the platform, so the explicit retry is the consent and the shared HITL dedupe key guards against double-executing a call that actually landed. A non-retryable failure is rejected with a machine-readable reason: a rejected token routes to a reconnect signal, a permanent failure to "not retryable". Requires PermContentUpdate — this drives external platform work, so a read-only viewer must not trigger it.
func (*AgentTaskHandler) StreamTasks ¶
func (h *AgentTaskHandler) StreamTasks(w http.ResponseWriter, r *http.Request)
StreamTasks handles GET /api/v1/tasks/stream — an SSE endpoint that pushes task lifecycle events (task.created, task.updated) for the authenticated user's business.
type AgentTaskService ¶
type AgentTaskService interface {
List(ctx context.Context, businessID uuid.UUID, filter domain.TaskFilter) ([]domain.AgentTask, int, error)
Retry(ctx context.Context, businessID uuid.UUID, taskID string) (*domain.AgentTask, error)
Dismiss(ctx context.Context, businessID uuid.UUID, taskID string) error
RerunVerification(ctx context.Context, businessID uuid.UUID, taskID string) (*domain.AgentTask, error)
}
AgentTaskService defines the interface for agent task operations.
type AuditLogDTO ¶
type AuditLogDTO = openapi.AuditEvent
AuditLogDTO is the historic local-name alias for the spec-side wire shape. Pointer fields are emitted as null when missing (no omitempty), matching the legacy struct semantics.
Details is `map[string]interface{}` in the spec (the pkg/audit RawMessage is decoded into a generic map for the wire). Re-encoding into the response envelope is semantically equivalent but NOT byte-identical with the upstream Details bytes: key order is alphabetic instead of struct- field order, and integer floats may be re-formatted by encoding/json. The UI consumes details as a generic JSON object so this is acceptable; no downstream service (orchestrator / billing / monitoring) reads the audit-list output bytes.
type AuditLogHandler ¶
type AuditLogHandler struct {
// contains filtered or unexported fields
}
AuditLogHandler implements GET /businesses/{id}/audit-logs.
func NewAuditLogHandler ¶
func NewAuditLogHandler(repo AuditLogLister) *AuditLogHandler
NewAuditLogHandler constructs an AuditLogHandler.
func (*AuditLogHandler) List ¶
func (h *AuditLogHandler) List(w http.ResponseWriter, r *http.Request)
List handles GET /api/v1/businesses/{id}/audit-logs. See docs/api/handlers/audit-log.md.
type AuditLogListResponse ¶
type AuditLogListResponse = openapi.AuditLogListResponse
AuditLogListResponse is the historic local-name alias for the spec-side list envelope. NextCursor is *string (omitempty in spec); the handler emits `"next_cursor":null` when the page is short of the limit by explicitly setting the field to nil (oapi-codegen drops the omitempty in the spec via the absence of x-go-type-extra-tags, so the field stays present in the JSON envelope).
type AuditLogLister ¶
type AuditLogLister interface {
ListByBusinessWithActors(ctx context.Context, businessID uuid.UUID, f domain.AuditLogFilter) ([]repository.AuditLogRow, error)
}
AuditLogLister is the narrow read surface the handler depends on. Exported so wire/ can type-assert domain.AuditLogRepository into this shape.
type AuthHandler ¶
type AuthHandler struct {
// contains filtered or unexported fields
}
AuthHandler handles authentication endpoints.
func NewAuthHandler ¶
func NewAuthHandler(userService UserService, secureCookies bool, auditLogger audit.Logger, jwtSecret []byte) (*AuthHandler, error)
NewAuthHandler creates a new auth handler instance.
func (*AuthHandler) ChangePassword ¶
func (h *AuthHandler) ChangePassword(w http.ResponseWriter, r *http.Request)
ChangePassword handles PUT /api/v1/auth/password.
func (*AuthHandler) ConfirmPasswordReset ¶
func (h *AuthHandler) ConfirmPasswordReset(w http.ResponseWriter, r *http.Request)
ConfirmPasswordReset handles POST /api/v1/auth/password-reset/confirm. On success the service has consumed the token, rotated the password hash, and wiped all refresh tokens for the user.
func (*AuthHandler) EmailBeforeVerify ¶
func (h *AuthHandler) EmailBeforeVerify(w http.ResponseWriter, r *http.Request)
EmailBeforeVerify handles PATCH /api/v1/auth/email-before-verify. Only allowed when email_verified=false; otherwise 403 email_already_verified.
func (*AuthHandler) Login ¶
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request)
Login layered defense: lockout middleware short-circuits TierLocked requests, captcha gate verifies X-Captcha-Token when middleware demands it, ErrInvalidCredentials increments the lockout counter, success clears it.
func (*AuthHandler) Logout ¶
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request)
Logout extracts user_id from refresh-token claims BEFORE the service invalidates the token in Redis — otherwise we lose the audit attribution.
func (*AuthHandler) Me ¶
func (h *AuthHandler) Me(w http.ResponseWriter, r *http.Request)
Me uses the deletion-aware getter when wired so users inside the 30-day grace window can still exercise restore.
func (*AuthHandler) RefreshToken ¶
func (h *AuthHandler) RefreshToken(w http.ResponseWriter, r *http.Request)
RefreshToken rotates the refresh-token cookie and mints a new access token.
func (*AuthHandler) Register ¶
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request)
Register handles user registration and auto-login. Validates the consents block against legalconfig.CurrentVersion(slug); missing/stale → 400 consent_required. On success, writes consents + user + verify token + outbox enqueue in the same tx.
func (*AuthHandler) RequestPasswordReset ¶
func (h *AuthHandler) RequestPasswordReset(w http.ResponseWriter, r *http.Request)
RequestPasswordReset handles POST /api/v1/auth/password-reset/request. Returns 204 ALWAYS regardless of whether the email is registered — the service handles per-email rate-limit + dummy-audit-on-unknown-email + outbox enqueue with symmetric timing across branches. The router also wraps this route in an IP-scoped rate limit (keyed on client IP + path, NOT on the email), which caps abusive mail-trigger volume from one source. Because that limiter throttles uniformly — 429 before the handler whether or not the email is registered — it does not weaken the no-enumeration parity: it leaks nothing about which addresses exist.
func (*AuthHandler) SetConsentDiffer ¶
func (h *AuthHandler) SetConsentDiffer(d ConsentDiffer)
SetConsentDiffer injects the ConsentService for /auth/me requiresReconsent.
func (*AuthHandler) SetEmailVerificationService ¶
func (h *AuthHandler) SetEmailVerificationService(s EmailVerificationServiceAPI)
SetEmailVerificationService injects the email-verification dependency post-construction.
func (*AuthHandler) SetMeUserExtraGetter ¶
func (h *AuthHandler) SetMeUserExtraGetter(fn func(ctx context.Context, userID uuid.UUID) (*domain.User, error))
SetMeUserExtraGetter injects the deletion-aware getter used by /auth/me.
func (*AuthHandler) SetPasswordResetService ¶
func (h *AuthHandler) SetPasswordResetService(s PasswordResetServiceAPI)
SetPasswordResetService injects the password-reset dependency post-construction.
func (*AuthHandler) UpdatePreferredLocale ¶
func (h *AuthHandler) UpdatePreferredLocale(w http.ResponseWriter, r *http.Request)
UpdatePreferredLocale handles PATCH /api/v1/auth/locale. Deliberately does NOT read Accept-Language — locale persistence is an explicit user action.
func (*AuthHandler) UpdateProfile ¶
func (h *AuthHandler) UpdateProfile(w http.ResponseWriter, r *http.Request)
UpdateProfile handles PATCH /api/v1/auth/profile. Currently persists the display name only; the request schema bounds it to 2..100 chars.
func (*AuthHandler) VerifyConfirm ¶
func (h *AuthHandler) VerifyConfirm(w http.ResponseWriter, r *http.Request)
VerifyConfirm handles POST /api/v1/auth/verify-email/confirm.
CRITICAL: NO Set-Cookie is emitted — no session is granted, so an attacker who registered with a victim's email cannot become the victim's session.
On invalid-failure we run a single follow-up LookupExpired to surface the "expired" UX hint — both codes are equally safe (token is burned either way).
func (*AuthHandler) VerifyResend ¶
func (h *AuthHandler) VerifyResend(w http.ResponseWriter, r *http.Request)
VerifyResend handles POST /api/v1/auth/verify-email/resend. Auth-required.
func (*AuthHandler) WithInviteOnlyRegistration ¶
func (h *AuthHandler) WithInviteOnlyRegistration(gate RegistrationAccessGate) *AuthHandler
WithInviteOnlyRegistration enables the closed-beta gate. Leaving it unset keeps registration open, which is the local-development default. A nil gate after enabling this mode fails closed instead of silently reopening signup.
func (*AuthHandler) WithLockout ¶
func (h *AuthHandler) WithLockout(lock *lockout.Lockout, captcha service.SmartCaptchaVerifier, failOpen bool) *AuthHandler
WithLockout installs the lockout + SmartCaptcha verifier. Optional — when not called the handler runs without lockout / captcha.
failOpen=true logs and proceeds on ErrCaptchaTransient (Yandex outage) so legit users aren't locked out during outages; false rejects as 403.
type BillingHandler ¶
type BillingHandler struct {
// contains filtered or unexported fields
}
BillingHandler implements GET /businesses/{id}/billing/summary.
func NewBillingHandler ¶
func NewBillingHandler(svc BillingSummarizer) *BillingHandler
NewBillingHandler constructs a BillingHandler.
func (*BillingHandler) Summary ¶
func (h *BillingHandler) Summary(w http.ResponseWriter, r *http.Request)
Summary handles GET /api/v1/businesses/{id}/billing/summary.
type BillingService ¶
type BillingService interface {
LogUsage(ctx context.Context, log *llm.UsageLog) error
GetDailySpend(ctx context.Context, businessID uuid.UUID, day time.Time) (float64, error)
}
BillingService is the narrow write-only surface the internal billing handler depends on. LogUsage backs the POST usage_logs endpoint; GetDailySpend backs the GET daily_spend endpoint that the orchestrator's rate-limiter consults before each chat turn.
type BillingSummarizer ¶
type BillingSummarizer interface {
Summary(ctx context.Context, businessID uuid.UUID) (service.BillingSummary, error)
}
BillingSummarizer is the narrow service surface the handler depends on.
type BusinessDeletionHandler ¶
type BusinessDeletionHandler struct {
// contains filtered or unexported fields
}
BusinessDeletionHandler owns DELETE /businesses/{id} + POST /businesses/{id}/restore.
func NewBusinessDeletionHandler ¶
func NewBusinessDeletionHandler(svc BusinessDeletionServiceAPI, allowedOrigins []string) *BusinessDeletionHandler
NewBusinessDeletionHandler constructs the handler. allowedOrigins holds the CORS_ALLOWED_ORIGINS values for Restore's Origin-header CSRF check.
func (*BusinessDeletionHandler) Delete ¶
func (h *BusinessDeletionHandler) Delete(w http.ResponseWriter, r *http.Request)
Delete handles DELETE /api/v1/businesses/{id}. Response codes: 204 / 403 not-owner|origin / 404 not-found / 423 already-pending.
CSRF defense: the Origin header MUST match an allowed CORS origin (mirrors Restore). The endpoint is side-effecting (schedules business deletion), so a CSRF-vulnerable path could trigger deletion from a hostile origin.
func (*BusinessDeletionHandler) Restore ¶
func (h *BusinessDeletionHandler) Restore(w http.ResponseWriter, r *http.Request)
Restore handles POST /api/v1/businesses/{id}/restore. Response codes: 204 / 403 not-owner|origin / 404 no-pending / 410 too-old.
CSRF defense: Origin header MUST match an allowed CORS origin (mirrors the account-restore endpoint).
type BusinessDeletionServiceAPI ¶
type BusinessDeletionServiceAPI interface {
RequestDeletion(ctx context.Context, actorUserID, businessID uuid.UUID, clientIP, userAgent string) error
CancelDeletion(ctx context.Context, actorUserID, businessID uuid.UUID, clientIP, userAgent string) error
GetScheduledDeletionAt(ctx context.Context, businessID uuid.UUID) (time.Time, error)
}
BusinessDeletionServiceAPI is the slice of *service.BusinessDeletionService the handler consumes. Declared as an interface so handler tests can pass an in-memory double.
type BusinessHandler ¶
type BusinessHandler struct {
// contains filtered or unexported fields
}
BusinessHandler handles business profile endpoints
func NewBusinessHandler ¶
func NewBusinessHandler(businessService BusinessService, syncer BusinessSyncer, objectStorage storage.Uploader) (*BusinessHandler, error)
NewBusinessHandler creates a new business handler instance. syncer may be nil; if provided, it is called asynchronously after each successful update. objectStorage may be nil in tests that do not exercise UploadLogo.
func (*BusinessHandler) CreateBusiness ¶
func (h *BusinessHandler) CreateBusiness(w http.ResponseWriter, r *http.Request)
CreateBusiness handles POST /api/v1/businesses (BIZ-03). Creates a new business and owner membership for the authenticated user.
func (*BusinessHandler) GetBusiness ¶
func (h *BusinessHandler) GetBusiness(w http.ResponseWriter, r *http.Request)
GetBusiness returns the business profile for the request's BusinessContext. Requires PermBusinessRead.
func (*BusinessHandler) GetBusinessToolApprovals ¶
func (h *BusinessHandler) GetBusinessToolApprovals(w http.ResponseWriter, r *http.Request)
GetBusinessToolApprovals handles GET /business/{id}/tool-approvals. Response shape: `{"toolApprovals": {"tool_name": "auto"|"manual", ...}}`. Absence from the map means the registry floor applies. Requires PermBusinessRead.
func (*BusinessHandler) GetDescriptionTemplate ¶
func (h *BusinessHandler) GetDescriptionTemplate(w http.ResponseWriter, r *http.Request)
GetDescriptionTemplate handles GET /business/{id}/description-template. Returns the stored template override (empty string when unset) plus the allowed placeholder names for the editor. Requires PermBusinessRead.
func (*BusinessHandler) GetDriftAlerts ¶
func (h *BusinessHandler) GetDriftAlerts(w http.ResponseWriter, r *http.Request)
GetDriftAlerts returns the explicit, default-off drift notification consent.
func (*BusinessHandler) GetOwnerBrief ¶
func (h *BusinessHandler) GetOwnerBrief(w http.ResponseWriter, r *http.Request)
GetOwnerBrief handles GET /businesses/{id}/owner-brief. Returns the stored weekly-owner-brief preferences. Default-on: a business that never set a preference reads back enabled=true, Monday 09:00. Requires PermBusinessRead.
func (*BusinessHandler) GetReviewAutopilot ¶
func (h *BusinessHandler) GetReviewAutopilot(w http.ResponseWriter, r *http.Request)
GetReviewAutopilot handles GET /business/{id}/review-autopilot. Returns the stored review-reply autopilot config. When unset the response is the default-off state {enabled:false, minRating:ReviewAutopilotMinRatingFloor}. Requires PermBusinessRead.
func (*BusinessHandler) GetVoiceProfile ¶
func (h *BusinessHandler) GetVoiceProfile(w http.ResponseWriter, r *http.Request)
GetVoiceProfile handles GET /business/{id}/voice-profile. Returns the stored brand-voice profile (empty string when unset). Requires PermBusinessRead.
func (*BusinessHandler) ListUserBusinesses ¶
func (h *BusinessHandler) ListUserBusinesses(w http.ResponseWriter, r *http.Request)
ListUserBusinesses handles GET /api/v1/businesses (BIZ-02). Returns the businesses the authenticated user is a member of, hydrated with business name + role. Auth-only (no BusinessContext needed — the user is not yet in a business scope).
func (*BusinessHandler) SetToolsCache ¶
func (h *BusinessHandler) SetToolsCache(c ToolsCache)
SetToolsCache wires a tools-registry cache for tool-name validation. Called after construction so existing NewBusinessHandler call sites don't churn. Safe to call with nil to disable the endpoints.
func (*BusinessHandler) UpdateBusiness ¶
func (h *BusinessHandler) UpdateBusiness(w http.ResponseWriter, r *http.Request)
UpdateBusiness updates the business profile for the request's BusinessContext. Requires PermBusinessUpdate.
func (*BusinessHandler) UpdateBusinessToolApprovals ¶
func (h *BusinessHandler) UpdateBusinessToolApprovals(w http.ResponseWriter, r *http.Request)
UpdateBusinessToolApprovals handles PUT /business/{id}/tool-approvals. Requires PermBusinessUpdate.
func (*BusinessHandler) UpdateDescriptionTemplate ¶
func (h *BusinessHandler) UpdateDescriptionTemplate(w http.ResponseWriter, r *http.Request)
UpdateDescriptionTemplate handles PUT /business/{id}/description-template. A non-empty template is a full replacement of the platform description; an empty string clears the override. Unknown placeholder tokens are rejected. On success the change is fanned out to connected platforms. Requires PermBusinessUpdate.
func (*BusinessHandler) UpdateDriftAlerts ¶
func (h *BusinessHandler) UpdateDriftAlerts(w http.ResponseWriter, r *http.Request)
UpdateDriftAlerts stores only driftAlerts, preserving every sibling setting.
func (*BusinessHandler) UpdateOwnerBrief ¶
func (h *BusinessHandler) UpdateOwnerBrief(w http.ResponseWriter, r *http.Request)
UpdateOwnerBrief handles PUT /businesses/{id}/owner-brief. Enables/disables the weekly owner-brief DM (enabled=false is the one-tap opt-out) and optionally sets the weekday/hour window. Absent fields keep their current stored value so a caller can toggle enabled without resupplying the schedule. Written via the same jsonb_set sub-key path as voiceProfile (no migration). Requires PermBusinessUpdate.
func (*BusinessHandler) UpdateReviewAutopilot ¶
func (h *BusinessHandler) UpdateReviewAutopilot(w http.ResponseWriter, r *http.Request)
UpdateReviewAutopilot handles PUT /business/{id}/review-autopilot. Stores the opt-in autopilot config. minRating is bounded to the positive range [ReviewAutopilotMinRatingFloor..5] so the setting can only RAISE the positive floor and can never be configured to auto-publish a negative or neutral review. Like the voice profile it is prompt/automation-only and is NOT fanned out to any connected platform. Requires PermBusinessUpdate.
func (*BusinessHandler) UpdateSchedule ¶
func (h *BusinessHandler) UpdateSchedule(w http.ResponseWriter, r *http.Request)
UpdateSchedule updates the business schedule (stored in settings). Requires PermBusinessUpdate.
func (*BusinessHandler) UpdateVoiceProfile ¶
func (h *BusinessHandler) UpdateVoiceProfile(w http.ResponseWriter, r *http.Request)
UpdateVoiceProfile handles PUT /business/{id}/voice-profile. A non-empty value is stored verbatim and governs both the chat loop and the review drafter; an empty string clears the override. Unlike the description template it is not fanned out to any platform — it changes prompt text only, not a platform-visible field. Requires PermBusinessUpdate.
func (*BusinessHandler) UpdateVoiceTone ¶
func (h *BusinessHandler) UpdateVoiceTone(w http.ResponseWriter, r *http.Request)
UpdateVoiceTone updates the business voice/tone tags (stored in settings). Body: {"tones": ["Тёплый", "Дружеский"]}. Requires PermBusinessUpdate.
func (*BusinessHandler) UploadLogo ¶
func (h *BusinessHandler) UploadLogo(w http.ResponseWriter, r *http.Request)
UploadLogo handles multipart logo upload, stores the file in object storage, and updates the business logo_url to the public URL. Requires PermBusinessUpdate.
type BusinessService ¶
type BusinessService interface {
Create(ctx context.Context, business *domain.Business, ownerUserID uuid.UUID) (*domain.Business, error)
GetByID(ctx context.Context, id uuid.UUID) (*domain.Business, error)
// Update applies a business profile edit; actorUserID is threaded through
// for the service-layer audit emission. It does not touch logo_url.
Update(ctx context.Context, business *domain.Business, actorUserID uuid.UUID) (*domain.Business, error)
// UpdateLogoURL writes only the logo_url via a targeted column update and
// returns the re-read row, so a concurrent profile edit cannot revert the
// new logo (and vice versa).
UpdateLogoURL(ctx context.Context, businessID uuid.UUID, url string, actorUserID uuid.UUID) (*domain.Business, error)
// UpdateSettingsKeys writes only the named settings sub-keys (schedule,
// voiceTone, specialDates) via a targeted jsonb_set and returns the re-read
// row. Sibling settings sub-keys (incl. tool_approvals) are preserved so a
// concurrent writer of a different sub-key is never reverted.
UpdateSettingsKeys(ctx context.Context, businessID uuid.UUID, keys map[string]interface{}, actorUserID uuid.UUID) (*domain.Business, error)
// ListMembershipsByUser powers GET /api/v1/businesses.
ListMembershipsByUser(ctx context.Context, userID uuid.UUID) ([]service.MembershipSummary, error)
// Tool-approval methods. Permission enforcement (PermBusinessRead /
// PermBusinessUpdate) is at the handler layer via authz.Can — the
// service is a thin data wrapper since (CLEAN-01).
GetToolApprovals(ctx context.Context, businessID uuid.UUID) (map[string]domain.ToolFloor, error)
UpdateToolApprovals(ctx context.Context, businessID uuid.UUID, approvals map[string]domain.ToolFloor) error
}
BusinessService defines the interface for business operations
type BusinessSyncer ¶
BusinessSyncer syncs updated business data to connected platforms.
type ChannelRequestHandler ¶
type ChannelRequestHandler struct {
// contains filtered or unexported fields
}
ChannelRequestHandler handles channel-demand capture.
func NewChannelRequestHandler ¶
func NewChannelRequestHandler(svc channelRequestService) *ChannelRequestHandler
NewChannelRequestHandler constructs a ChannelRequestHandler.
func (*ChannelRequestHandler) Create ¶
func (h *ChannelRequestHandler) Create(w http.ResponseWriter, r *http.Request)
Create handles POST /businesses/{id}/channel-requests. The enum + note-length checks are enforced by decodeAndValidate from the spec-derived validate tags; the service re-validates the channel as defense in depth.
func (*ChannelRequestHandler) List ¶
func (h *ChannelRequestHandler) List(w http.ResponseWriter, r *http.Request)
List handles GET /businesses/{id}/channel-requests, returning the per-channel demand aggregate for the caller's business.
type ChatProxyHandler ¶
type ChatProxyHandler struct {
// contains filtered or unexported fields
}
ChatProxyHandler is a thin HTTP-facade over chatturn.Turn — parses, gates, maps TurnOutcome to HTTP. See docs/api/handlers/chat-proxy.md.
messageRepo is retained for one legacy test (TestChatProxy_LoadHistory_SkipsEmptyAssistant) that constructs the handler via struct literal; loadHistory reads it directly so the test does not need a fully-wired *chatturn.Turn.
func NewChatProxyHandler ¶
func NewChatProxyHandler( businessService BusinessService, integrationService IntegrationService, projectService ProjectService, conversationRepo domain.ConversationRepository, messageRepo domain.MessageRepository, pendingRepo domain.PendingToolCallRepository, postRepo domain.PostRepository, reviewRepo domain.ReviewRepository, agentTaskRepo domain.AgentTaskRepository, taskHub *taskhub.Hub, orchClient *orchestratorclient.Client, titler *service.Titler, auditLogger audit.Logger, historyLimit int, ) *ChatProxyHandler
NewChatProxyHandler builds the chatturn.Turn from its dependency set. See docs/api/handlers/chat-proxy.md §"Construction".
func (*ChatProxyHandler) Chat ¶
func (h *ChatProxyHandler) Chat(w http.ResponseWriter, r *http.Request)
Chat handles POST /chat/{conversationID}; delegates lifecycle to Turn.Run and maps TurnOutcome → HTTP. See docs/api/handlers/chat-proxy.md.
func (*ChatProxyHandler) SetSSECounter ¶
func (h *ChatProxyHandler) SetSSECounter(c *ssecounter.Counter, defaultTier string)
SetSSECounter wires the per-user SSE concurrency cap (optional).
func (*ChatProxyHandler) Turn ¶
func (h *ChatProxyHandler) Turn() *chatturn.Turn
Turn exposes the shared chat-turn lifecycle so sibling handlers (HITLHandler.Resume) can delegate to the same persisting resume path instead of building a second Turn.
type ConsentDiffer ¶
type ConsentDiffer interface {
DiffAgainstCurrent(ctx context.Context, userID uuid.UUID) (*service.RequiresReconsentInfo, error)
}
ConsentDiffer powers the /auth/me requiresReconsent field. nil-safe.
type ConsentsHandler ¶
type ConsentsHandler struct {
// contains filtered or unexported fields
}
ConsentsHandler owns the three consent endpoints.
func NewConsentsHandler ¶
func NewConsentsHandler(svc ConsentsServiceAPI, deletion AccountDeletionServiceAPI, repo ConsentsListerAPI, allowedOrigins []string) *ConsentsHandler
NewConsentsHandler constructs the consent handler. The allowedOrigins slice is the CORS_ALLOWED_ORIGINS values — used for the Origin-header CSRF check on the two side-effecting endpoints. deletion supplies the scheduled deletion timestamp surfaced in the PDN-withdrawal response (shared with UserDeletionHandler).
func (*ConsentsHandler) ListMine ¶
func (h *ConsentsHandler) ListMine(w http.ResponseWriter, r *http.Request)
ListMine handles GET /users/me/consents. Returns the user's current consent state for Surface F (Withdraw / status panel).
func (*ConsentsHandler) Reconsent ¶
func (h *ConsentsHandler) Reconsent(w http.ResponseWriter, r *http.Request)
Reconsent handles POST /auth/consents.
204 on success; 400 consent_required when a slug is missing / validation fails before the service runs; 409 version_mismatch when the service detects the build's currentVersion has bumped since the submitted version; 403 origin_not_allowed when the Origin header is blank or unmatched (CSRF discipline).
func (*ConsentsHandler) WithdrawPDN ¶
func (h *ConsentsHandler) WithdrawPDN(w http.ResponseWriter, r *http.Request)
WithdrawPDN handles POST /users/me/consents/pdn/withdraw.
200 on success and 423 account_pending_deletion (already in the grace window) both return a PendingDeletionResponse carrying the real scheduled deletion date; 403 when the Origin header is missing/unmatched; 404 when the user row is gone.
type ConsentsListerAPI ¶
type ConsentsListerAPI interface {
ListByUser(ctx context.Context, userID uuid.UUID) ([]repository.Consent, error)
}
ConsentsListerAPI is the slice of UserConsentsRepository the handler uses for GET /users/me/consents. Kept separate from ConsentsServiceAPI because the service layer doesn't need a List* method — the read path is repo-direct.
type ConsentsServiceAPI ¶
type ConsentsServiceAPI interface {
ReConsent(ctx context.Context, userID uuid.UUID, ip, userAgent string, policies []service.PolicyAccepted) error
WithdrawPDN(ctx context.Context, userID uuid.UUID, ip, userAgent string) error
}
ConsentsServiceAPI is the slice of *service.ConsentService the handler consumes. Declared as an interface so handler tests can pass an in-memory double (mirrors the rest of the service-API boundaries).
type ContentTemplateHandler ¶
type ContentTemplateHandler struct {
// contains filtered or unexported fields
}
func NewContentTemplateHandler ¶
func NewContentTemplateHandler(svc *service.ContentTemplateService) (*ContentTemplateHandler, error)
func (*ContentTemplateHandler) Create ¶
func (h *ContentTemplateHandler) Create(w http.ResponseWriter, r *http.Request)
func (*ContentTemplateHandler) CreateFromPost ¶
func (h *ContentTemplateHandler) CreateFromPost(w http.ResponseWriter, r *http.Request)
func (*ContentTemplateHandler) CreateFromReview ¶
func (h *ContentTemplateHandler) CreateFromReview(w http.ResponseWriter, r *http.Request)
func (*ContentTemplateHandler) Delete ¶
func (h *ContentTemplateHandler) Delete(w http.ResponseWriter, r *http.Request)
func (*ContentTemplateHandler) Get ¶
func (h *ContentTemplateHandler) Get(w http.ResponseWriter, r *http.Request)
func (*ContentTemplateHandler) List ¶
func (h *ContentTemplateHandler) List(w http.ResponseWriter, r *http.Request)
func (*ContentTemplateHandler) Render ¶
func (h *ContentTemplateHandler) Render(w http.ResponseWriter, r *http.Request)
func (*ContentTemplateHandler) Update ¶
func (h *ContentTemplateHandler) Update(w http.ResponseWriter, r *http.Request)
type ConversationHandler ¶
type ConversationHandler struct {
// contains filtered or unexported fields
}
ConversationHandler serves the conversation CRUD + move/pin/unpin/list-messages surface. See docs/api/handlers/conversation.md.
func NewConversationHandler ¶
func NewConversationHandler( conversationRepo domain.ConversationRepository, messageRepo domain.MessageRepository, businessService BusinessService, projectService ProjectService, conversationService ConversationService, ) (*ConversationHandler, error)
NewConversationHandler constructs a ConversationHandler; all five deps are required.
func (*ConversationHandler) CreateConversation ¶
func (h *ConversationHandler) CreateConversation(w http.ResponseWriter, r *http.Request)
CreateConversation handles POST /conversations. See docs/api/handlers/conversation.md.
func (*ConversationHandler) DeleteConversation ¶
func (h *ConversationHandler) DeleteConversation(w http.ResponseWriter, r *http.Request)
DeleteConversation handles DELETE /conversations/{id}. See docs/api/handlers/conversation.md.
func (*ConversationHandler) GetConversation ¶
func (h *ConversationHandler) GetConversation(w http.ResponseWriter, r *http.Request)
GetConversation handles GET /conversations/{id}. See docs/api/handlers/conversation.md.
func (*ConversationHandler) ListConversations ¶
func (h *ConversationHandler) ListConversations(w http.ResponseWriter, r *http.Request)
ListConversations handles GET /conversations. See docs/api/handlers/conversation.md.
func (*ConversationHandler) ListMessages ¶
func (h *ConversationHandler) ListMessages(w http.ResponseWriter, r *http.Request)
ListMessages handles GET /conversations/{id}/messages — adapter over ConversationService.OpenChat. See docs/api/handlers/conversation.md.
func (*ConversationHandler) MoveConversation ¶
func (h *ConversationHandler) MoveConversation(w http.ResponseWriter, r *http.Request)
MoveConversation handles POST /conversations/{id}/move. See docs/api/handlers/conversation.md. Null/empty/absent ProjectId all map to the "Без проекта" bucket.
func (*ConversationHandler) Pin ¶
func (h *ConversationHandler) Pin(w http.ResponseWriter, r *http.Request)
Pin handles POST /conversations/{id}/pin — atomic pinned_at=now scoped by (id, business_id, user_id). Cross-tenant → uniform 404 (no leak). See docs/api/handlers/conversation.md §"Pin / unpin atomicity".
func (*ConversationHandler) Unpin ¶
func (h *ConversationHandler) Unpin(w http.ResponseWriter, r *http.Request)
Unpin handles POST /conversations/{id}/unpin — symmetric to Pin. See docs/api/handlers/conversation.md §"Pin / unpin atomicity".
func (*ConversationHandler) UpdateConversation ¶
func (h *ConversationHandler) UpdateConversation(w http.ResponseWriter, r *http.Request)
UpdateConversation handles PUT /conversations/{id} — manual rename. See docs/api/handlers/conversation.md §"UpdateConversation".
type ConversationService ¶
type ConversationService interface {
List(ctx context.Context, businessID, userID uuid.UUID, limit, offset int) ([]domain.Conversation, error)
MoveToProject(
ctx context.Context,
conversationID string,
businessID, requesterUserID uuid.UUID,
projectID *string,
) (*domain.Conversation, error)
OpenChat(
ctx context.Context,
conversationID string,
businessID, requesterUserID uuid.UUID,
) (*service.ChatView, error)
DeleteWithMessages(ctx context.Context, conversationID string) error
}
ConversationService is the handler-facing view of the *service.ConversationService concrete type. Owns conversation operations that cross multiple repository reads/writes: - MoveToProject — multi-repo write transition (move + system note). - OpenChat — multi-repo read composite (messages + pending batches) returning the fully-projected ChatView the handler JSON-encodes verbatim.
Declared as an interface so conversation_test.go can swap in a noop fake for handler tests that don't exercise these paths.
type EmailVerificationServiceAPI ¶
type EmailVerificationServiceAPI interface {
RequestResend(ctx context.Context, userID uuid.UUID) error
ConfirmVerify(ctx context.Context, plaintextToken string) (uuid.UUID, error)
IsTokenExpired(ctx context.Context, plaintextToken string) (bool, error)
ChangeEmailBeforeVerify(ctx context.Context, userID uuid.UUID, newEmail string) (oldEmail string, err error)
}
EmailVerificationServiceAPI is the AuthHandler's view of EmailVerificationService.
type ErrorResponse ¶
type ErrorResponse = openapi.ErrorResponse
Re-export spec-owned error envelopes under the historic handler.* names so the existing test suite continues to compile against named handler types. Wire shape (keys + nesting) is identical to the legacy local definitions — both `error` and `fields` are required, matching writeJSONError / writeValidationError output.
type FeedbackHandler ¶
type FeedbackHandler struct {
// contains filtered or unexported fields
}
FeedbackHandler handles in-app feedback submissions.
func NewFeedbackHandler ¶
func NewFeedbackHandler(svc feedbackSubmitter) *FeedbackHandler
NewFeedbackHandler constructs a FeedbackHandler.
func (*FeedbackHandler) Submit ¶
func (h *FeedbackHandler) Submit(w http.ResponseWriter, r *http.Request)
Submit handles POST /feedback.
type HITLHandler ¶
type HITLHandler struct {
// contains filtered or unexported fields
}
HITLHandler serves the three HITL HTTP endpoints. See docs/api/handlers/hitl.md.
func NewHITLHandler ¶
func NewHITLHandler( hitlService *service.HITLService, businessService BusinessService, conversationRepo domain.ConversationRepository, integrationRepo integrationLister, resumer chatResumer, ) (*HITLHandler, error)
NewHITLHandler constructs a HITLHandler; all deps are required. resumer is the shared chat-turn lifecycle — Resume delegates to it so the approved-tool result is persisted (the bare SSE proxy this replaces never wrote it back). integrationRepo lets Resume derive the active-platform list the orchestrator needs to keep post-approval platform tools available.
func (*HITLHandler) GetTools ¶
func (h *HITLHandler) GetTools(w http.ResponseWriter, r *http.Request)
GetTools handles GET /tools — registry projection via 5-min cache. See docs/api/handlers/hitl.md §"GetTools projection".
func (*HITLHandler) ResolvePendingToolCalls ¶
func (h *HITLHandler) ResolvePendingToolCalls(w http.ResponseWriter, r *http.Request)
ResolvePendingToolCalls handles POST /conversations/{id}/pending-tool-calls/{batch_id}/resolve. See docs/api/handlers/hitl.md.
func (*HITLHandler) Resume ¶
func (h *HITLHandler) Resume(w http.ResponseWriter, r *http.Request)
Resume handles POST /chat/{id}/resume?batch_id=X. See docs/api/handlers/hitl.md.
func (*HITLHandler) SetAuditLogger ¶
func (h *HITLHandler) SetAuditLogger(auditLogger audit.Logger)
SetAuditLogger wires the audit logger used to record in-app approval resolutions. A nil logger degrades to audit.Nop() so the handler never panics when audit is unconfigured (tests). Mirrors the off-app TelegramApprovalConsumer, so an in-app resolve is attributable to the acting owner + channel exactly as an off-app tap is.
func (*HITLHandler) SetSSECounter ¶
func (h *HITLHandler) SetSSECounter(c *ssecounter.Counter, defaultTier string)
SetSSECounter wires the per-user SSE concurrency cap (optional). Mirrors ChatProxyHandler.SetSSECounter so the chat and resume streams share one cap.
type IntegrationHandler ¶
type IntegrationHandler struct {
// contains filtered or unexported fields
}
IntegrationHandler handles integration endpoints
func NewIntegrationHandler ¶
func NewIntegrationHandler(integrationService IntegrationService, businessService BusinessService, auditLogger audit.Logger) (*IntegrationHandler, error)
NewIntegrationHandler creates a new integration handler instance.
businessService lets DeleteIntegration reject disconnects against a soft-deleted (erasure-pending) organization, which the RequireBusinessAccess middleware does not filter.
Integration audit events are emitted by the service layer.
func (*IntegrationHandler) DeleteIntegration ¶
func (h *IntegrationHandler) DeleteIntegration(w http.ResponseWriter, r *http.Request)
DeleteIntegration deletes an integration by ID
func (*IntegrationHandler) GetIntegrationsDrift ¶
func (h *IntegrationHandler) GetIntegrationsDrift(w http.ResponseWriter, r *http.Request)
GetIntegrationsDrift returns the proactive-sync drift status for every connected channel of the business. Read-gated on PermIntegrationsRead like ListIntegrations. Returns an empty list when reconciliation is unwired.
func (*IntegrationHandler) ListIntegrations ¶
func (h *IntegrationHandler) ListIntegrations(w http.ResponseWriter, r *http.Request)
ListIntegrations returns all integrations for the business from the request context.
func (*IntegrationHandler) SetHealthChecker ¶
func (h *IntegrationHandler) SetHealthChecker(checker connHealthChecker)
SetHealthChecker injects the connection-health checker the verify endpoint uses to compute the synchronous per-integration health array. Nil-safe; mirrors SetReconciler so the constructor and its test call sites are unchanged. When unwired the verify endpoint behaves exactly as before.
func (*IntegrationHandler) SetReconciler ¶
func (h *IntegrationHandler) SetReconciler(reconciler driftLister, syncTrigger businessSyncTrigger)
SetReconciler injects the proactive-sync collaborators that back the drift + verify endpoints. Both may be nil (reconciliation unwired); the endpoints degrade gracefully. Called from wire/handlers.go after construction so the existing constructor signature (and its many test call sites) is unchanged.
func (*IntegrationHandler) VerifyIntegrations ¶
func (h *IntegrationHandler) VerifyIntegrations(w http.ResponseWriter, r *http.Request)
VerifyIntegrations performs manual repair: it re-pushes the stored business profile to every connected platform (the SAME re-push the reactive path uses) and reschedules the channels for an immediate re-check. Write-gated on PermBusinessUpdate because it mutates the remote platforms.
type IntegrationService ¶
type IntegrationService interface {
ListByBusinessID(ctx context.Context, businessID uuid.UUID) ([]domain.Integration, error)
GetByBusinessAndPlatform(ctx context.Context, businessID uuid.UUID, platform string) (*domain.Integration, error)
Delete(ctx context.Context, integrationID uuid.UUID, actorID uuid.UUID) error
MarkTokenExpired(ctx context.Context, businessID uuid.UUID, platform, externalID string) error
}
IntegrationService defines the interface for integration operations
type InternalBillingHandler ¶
type InternalBillingHandler struct {
// contains filtered or unexported fields
}
InternalBillingHandler serves POST /internal/v1/billing/usage_logs on the mTLS-protected :8443 listener. It is reached only via the orchestrator's pkg/billingclient, gated by router-level middleware.RequireServiceIdentity.
func NewInternalBillingHandler ¶
func NewInternalBillingHandler(b BillingService, log *slog.Logger) *InternalBillingHandler
NewInternalBillingHandler wires the handler with a backing BillingService (production: services/api/internal/repository.billingRepository). A nil logger falls back to slog.Default().
func (*InternalBillingHandler) GetDailySpend ¶
func (h *InternalBillingHandler) GetDailySpend(w http.ResponseWriter, r *http.Request)
GetDailySpend handles GET /internal/v1/billing/daily_spend.
Query parameters (both required):
- business_id — UUID of the business whose spend to look up.
- date — UTC calendar day in YYYY-MM-DD form.
Status codes:
- 200 OK — body is {"daily_spend_usd": <float>}.
- 400 Bad Request — missing/invalid business_id or date; body is {"error":"invalid_payload","detail":"…"}.
- 405 Method Not Allowed — non-GET verb.
- 500 Internal Server Error — repository failure; body is {"error":"transient"} (no internal details leaked).
func (*InternalBillingHandler) LogUsage ¶
func (h *InternalBillingHandler) LogUsage(w http.ResponseWriter, r *http.Request)
LogUsage handles POST /internal/v1/billing/usage_logs.
Status codes:
- 204 No Content — success.
- 400 Bad Request — invalid JSON, missing required fields, or negative token counts. Body is {"error":"invalid_payload","detail":"…"}.
- 405 Method Not Allowed — non-POST verb.
- 500 Internal Server Error — repository failure. Body is {"error":"transient"} (no internal details leaked).
type InternalTokenHandler ¶
type InternalTokenHandler struct {
// contains filtered or unexported fields
}
InternalTokenHandler handles internal token endpoints.
func NewInternalTokenHandler ¶
func NewInternalTokenHandler(tokenService TokenService) *InternalTokenHandler
NewInternalTokenHandler creates a new InternalTokenHandler instance
func (*InternalTokenHandler) GetToken ¶
func (h *InternalTokenHandler) GetToken(w http.ResponseWriter, r *http.Request)
GetToken handles GET /internal/v1/tokens Query params: business_id (required), platform (required), external_id (optional)
type InternalYandexSharedHandler ¶
type InternalYandexSharedHandler struct {
// contains filtered or unexported fields
}
InternalYandexSharedHandler sets/rotates the single shared representative session used by delegated-representative access. It is served ONLY on the mTLS-protected internal listener behind a service-identity allowlist — the ops/admin bootstrap path. It never touches customer credentials.
func NewInternalYandexSharedHandler ¶
func NewInternalYandexSharedHandler(setter sharedSessionSetter, sharedBusinessID string) *InternalYandexSharedHandler
NewInternalYandexSharedHandler constructs the bootstrap handler. sharedBusinessID is the config sentinel (YANDEX_SHARED_BUSINESS_ID); an empty value makes the endpoint fail closed ("delegated access not configured").
func (*InternalYandexSharedHandler) SetSharedSession ¶
func (h *InternalYandexSharedHandler) SetSharedSession(w http.ResponseWriter, r *http.Request)
SetSharedSession handles POST /internal/v1/yandex/shared-session. It validates the pasted cookies, then stores/rotates them as the KMS-wrapped shared singleton under the sentinel business.
type InvitationsHandler ¶
type InvitationsHandler struct {
// contains filtered or unexported fields
}
InvitationsHandler serves the invitation CRUD + accept endpoints.
func NewInvitationsHandler ¶
func NewInvitationsHandler( ir domain.InvitationRepository, mr domain.BusinessMembershipRepository, rr domain.RoleRepository, ur domain.UserRepository, br domain.BusinessRepository, bs businessGetter, pool poolBeginner, inv memberCacheInvalidator, auditLogger audit.Logger, ) (*InvitationsHandler, error)
NewInvitationsHandler constructs an InvitationsHandler; every dep is required. businessService lets the write endpoints reject mutations against a soft-deleted (erasure-pending) organization that RequireBusinessAccess (membership-only) does not gate.
func (*InvitationsHandler) Accept ¶
func (h *InvitationsHandler) Accept(w http.ResponseWriter, r *http.Request)
Accept handles POST /api/v1/invitations/{token}/accept — auth-required, NOT business-scoped. See docs/api/handlers/invitations.md for the race-safe accept order.
func (*InvitationsHandler) Create ¶
func (h *InvitationsHandler) Create(w http.ResponseWriter, r *http.Request)
Create handles POST /api/v1/businesses/{id}/invitations. See docs/api/handlers/invitations.md.
func (*InvitationsHandler) ListPending ¶
func (h *InvitationsHandler) ListPending(w http.ResponseWriter, r *http.Request)
ListPending handles GET /api/v1/businesses/{id}/invitations. See docs/api/handlers/invitations.md. ListPending includes inviters in their account deletion grace window.
func (*InvitationsHandler) Preview ¶
func (h *InvitationsHandler) Preview(w http.ResponseWriter, r *http.Request)
Preview handles GET /api/v1/invitations/{token} — PUBLIC; the token IS the auth. See docs/api/handlers/invitations.md.
func (*InvitationsHandler) Revoke ¶
func (h *InvitationsHandler) Revoke(w http.ResponseWriter, r *http.Request)
Revoke handles DELETE /api/v1/businesses/{id}/invitations/{inviteId}. See docs/api/handlers/invitations.md.
type LandingHandler ¶
type LandingHandler struct {
// contains filtered or unexported fields
}
LandingHandler handles public marketing-landing capture.
func NewLandingHandler ¶
func NewLandingHandler(svc landingService) *LandingHandler
NewLandingHandler constructs a LandingHandler.
func (*LandingHandler) JoinWaitlist ¶
func (h *LandingHandler) JoinWaitlist(w http.ResponseWriter, r *http.Request)
JoinWaitlist handles POST /waitlist. The email/enum/consent checks are enforced by decodeAndValidate from the spec-derived validate tags; the service re-validates and normalizes as defense in depth.
func (*LandingHandler) RecordChannelVote ¶
func (h *LandingHandler) RecordChannelVote(w http.ResponseWriter, r *http.Request)
RecordChannelVote handles POST /channel-votes.
func (*LandingHandler) RecordLandingEvent ¶
func (h *LandingHandler) RecordLandingEvent(w http.ResponseWriter, r *http.Request)
RecordLandingEvent handles POST /landing-events.
type LoginRequest ¶
type LoginRequest = openapi.LoginRequest
Re-export spec-owned shapes under the historic handler.* names.
type LoginResponse ¶
type LoginResponse = openapi.LoginResponse
Re-export spec-owned shapes under the historic handler.* names.
type MeResponse ¶
type MeResponse = openapi.MeResponse
MeResponse is the spec-owned wire shape for GET /auth/me. The historic handler-local struct that embedded *domain.User is gone; the spec (docs/api/spec/openapi.yaml#/components/schemas/MeResponse) is now the sole source of truth for the field set. Mappers below copy only the five wire-safe fields from domain.User — never the password hash, the email_verified_at timestamp, or the soft-delete-state timestamps that domain.User carries with json:"-".
type MembersHandler ¶
type MembersHandler struct {
// contains filtered or unexported fields
}
MembersHandler implements MEMBER-01..06:
GET /businesses/{id}/members → ListMembers (MEMBER-01)
PATCH /businesses/{id}/members/{userId} → UpdateMemberRole (MEMBER-02 + MEMBER-05 + MEMBER-06)
DELETE /businesses/{id}/members/{userId} → RemoveMember (MEMBER-03 + MEMBER-04 + MEMBER-05)
func NewMembersHandler ¶
func NewMembersHandler( mr domain.BusinessMembershipRepository, rr domain.RoleRepository, ur domain.UserRepository, ir domain.InvitationRepository, bs businessGetter, pool poolBeginner, inv memberCacheInvalidator, auditLogger audit.Logger, ) (*MembersHandler, error)
NewMembersHandler constructs a MembersHandler. All dependencies are required.
adds `auditLogger` so PATCH/DELETE member endpoints can emit rbac.role_granted / rbac.member_removed audit events AFTER the underlying transaction commits. businessService lets the write endpoints reject mutations against a soft-deleted (erasure-pending) organization that RequireBusinessAccess (membership-only) does not gate.
func (*MembersHandler) ListMembers ¶
func (h *MembersHandler) ListMembers(w http.ResponseWriter, r *http.Request)
ListMembers handles GET /api/v1/businesses/{id}/members. Includes members in their account deletion grace window.
func (*MembersHandler) RemoveMember ¶
func (h *MembersHandler) RemoveMember(w http.ResponseWriter, r *http.Request)
RemoveMember handles DELETE /api/v1/businesses/{id}/members/{userId}. Self-removal is permission-exempt but remains subject to the service last-owner check.
func (*MembersHandler) UpdateMemberRole ¶
func (h *MembersHandler) UpdateMemberRole(w http.ResponseWriter, r *http.Request)
UpdateMemberRole handles PATCH /api/v1/businesses/{id}/members/{userId}. The membership service validates the role and commits the mutation with invitation revocation.
type PasswordResetServiceAPI ¶
type PasswordResetServiceAPI interface {
RequestReset(ctx context.Context, emailAddr, clientIP, userAgent string) error
ConfirmReset(ctx context.Context, plaintextToken, newPassword, clientIP, userAgent string) error
}
PasswordResetServiceAPI is the AuthHandler's view of PasswordResetService.
type PermissionsHandler ¶
type PermissionsHandler struct{}
PermissionsHandler exposes the static permission registry as a JSON endpoint. ships GET /api/v1/permissions; the endpoint sits behind the existing authMiddleware (any authenticated user) per AUTHZ-01 / CONTEXT decision.
The handler holds no state: AllPermissions returns a fresh slice on every call and the body is computed in the request goroutine.
func NewPermissionsHandler ¶
func NewPermissionsHandler() *PermissionsHandler
NewPermissionsHandler constructs a zero-dependency handler. It exists for symmetry with the project's NewXxxHandler convention; future versions (descriptions) may take dependencies and benefit from the seam.
func (*PermissionsHandler) List ¶
func (h *PermissionsHandler) List(w http.ResponseWriter, r *http.Request)
List returns the permission registry as JSON. Auth is enforced upstream by the existing authMiddleware (router wiring in Plan G).
Descriptions are localized per request locale via the i18n catalog (permissions.<resource>.<action>.desc). The registry's hardcoded RU Description is the fallback when a catalog key is absent.
GET /api/v1/permissions
200 -> openapi.PermissionRegistryResponse 401 -> handled by authMiddleware before we get here
type PlatformAvailability ¶
PlatformAvailability tells the PlatformsHandler which integration platforms have their credentials configured in the current deployment. The handler uses this to downgrade the default Active status to OAuthNotConfigured for platforms whose credentials are missing, so the frontend can hide broken connect flows.
Each field is true iff the minimum credentials required to run the corresponding flow are present (non-empty). Telegram uses a bot token rather than OAuth; the field name keeps the OAuth-centric naming for frontend symmetry.
type PlatformsHandler ¶
type PlatformsHandler struct {
// contains filtered or unexported fields
}
PlatformsHandler exposes the canonical integration platform registry.
func NewPlatformsHandler ¶
func NewPlatformsHandler(availability PlatformAvailability) *PlatformsHandler
NewPlatformsHandler creates a PlatformsHandler that reports the registry merged with the supplied availability flags.
func (*PlatformsHandler) List ¶
func (h *PlatformsHandler) List(w http.ResponseWriter, r *http.Request)
List handles GET /api/v1/platforms.
The endpoint is intentionally unauthenticated so the marketing landing page can render the list of supported platforms without prompting for login. The returned data is non-sensitive — only platform names, descriptions, and a derived availability status, no credentials and no per-tenant data.
type PostHandler ¶
type PostHandler struct {
// contains filtered or unexported fields
}
PostHandler handles post-related HTTP requests
func NewPostHandler ¶
func NewPostHandler(postService PostService) (*PostHandler, error)
NewPostHandler creates a new post handler instance
func (*PostHandler) GetPost ¶
func (h *PostHandler) GetPost(w http.ResponseWriter, r *http.Request)
GetPost handles GET /api/v1/posts/{id}
func (*PostHandler) ListPosts ¶
func (h *PostHandler) ListPosts(w http.ResponseWriter, r *http.Request)
ListPosts handles GET /api/v1/posts
type PostService ¶
type PostService interface {
List(ctx context.Context, businessID uuid.UUID, filter domain.PostFilter) ([]domain.Post, int, error)
GetByID(ctx context.Context, businessID uuid.UUID, id string) (*domain.Post, error)
}
PostService defines the interface for post operations.
type PresenceHealthHandler ¶
type PresenceHealthHandler struct {
// contains filtered or unexported fields
}
PresenceHealthHandler serves the read-only composite presence-health score.
func NewPresenceHealthHandler ¶
func NewPresenceHealthHandler(scorer PresenceHealthScorer, snapshots PresenceHealthSnapshotStore) (*PresenceHealthHandler, error)
NewPresenceHealthHandler constructs the handler over the scorer + snapshot store. A nil dependency is a wiring bug and returns an error.
func (*PresenceHealthHandler) GetPresenceHealth ¶
func (h *PresenceHealthHandler) GetPresenceHealth(w http.ResponseWriter, r *http.Request)
GetPresenceHealth handles GET /businesses/{id}/presence-health. It returns the composite 0-100 presence-health score, the week-over-week trend delta, the four normalized sub-scores, the weight set used, and a single next-action recommendation. Aggregate-only: no author name, review text, or reply text is fetched or returned. Authz mirrors the other GET review endpoints: RequireBusinessAccess + content:read.
It reads the trend from the most-recent PRIOR-week snapshot and, when the current score has a composite, lazily upserts this week's snapshot so the trend is fresh without waiting for the weekly worker — the prior-week read excludes the current week, so this lazy write can never become its own baseline.
type PresenceHealthScorer ¶
type PresenceHealthScorer interface {
Score(ctx context.Context, businessID uuid.UUID, targetHours int) (service.PresenceHealthScore, error)
}
PresenceHealthScorer computes the composite presence-health score for one business. *service.PresenceHealthService satisfies it.
type PresenceHealthSnapshotStore ¶
type PresenceHealthSnapshotStore interface {
GetMostRecentPrior(ctx context.Context, businessID uuid.UUID, currentWeek string) (*domain.PresenceHealthSnapshot, error)
Upsert(ctx context.Context, snap domain.PresenceHealthSnapshot) error
}
PresenceHealthSnapshotStore reads the prior-week snapshot (for the trend delta) and upserts the current week's snapshot (the optional lazy write on read). domain.PresenceHealthSnapshotRepository satisfies it.
type ProjectHandler ¶
type ProjectHandler struct {
// contains filtered or unexported fields
}
ProjectHandler serves the /api/v1/projects REST endpoints.
func NewProjectHandler ¶
func NewProjectHandler(ps ProjectService, bs businessGetter) (*ProjectHandler, error)
NewProjectHandler constructs a ProjectHandler. Both the projectService and businessService dependencies are required. Business scoping is handled by the RequireBusinessAccess middleware which injects BusinessContext into every request; the businessService lets the write endpoints additionally reject mutations against a soft-deleted organization.
func (*ProjectHandler) ConversationCount ¶
func (h *ProjectHandler) ConversationCount(w http.ResponseWriter, r *http.Request)
ConversationCount handles GET /api/v1/projects/{id}/conversation-count. Feeds the frontend delete-confirmation dialog so the user sees how many chats will be destroyed before confirming.
func (*ProjectHandler) Create ¶
func (h *ProjectHandler) Create(w http.ResponseWriter, r *http.Request)
Create handles POST /api/v1/projects.
func (*ProjectHandler) Delete ¶
func (h *ProjectHandler) Delete(w http.ResponseWriter, r *http.Request)
Delete handles DELETE /api/v1/projects/{id}. Hard-deletes the project plus every Mongo conversation/message assigned to it, returning the counts so the frontend can show "deleted N chats" feedback.
func (*ProjectHandler) Get ¶
func (h *ProjectHandler) Get(w http.ResponseWriter, r *http.Request)
Get handles GET /api/v1/projects/{id}.
func (*ProjectHandler) List ¶
func (h *ProjectHandler) List(w http.ResponseWriter, r *http.Request)
List handles GET /api/v1/projects.
func (*ProjectHandler) SetToolsCache ¶
func (h *ProjectHandler) SetToolsCache(c ToolsCache)
SetToolsCache wires a tools-registry cache so PUT /projects/{id} can validate approvalOverrides keys against the live orchestrator registry. Safe to call with nil to disable the field.
func (*ProjectHandler) Update ¶
func (h *ProjectHandler) Update(w http.ResponseWriter, r *http.Request)
Update handles PUT /api/v1/projects/{id}.
type ProjectService ¶
type ProjectService interface {
Create(ctx context.Context, businessID, actorID uuid.UUID, input service.CreateProjectInput) (*domain.Project, error)
GetByID(ctx context.Context, businessID, id uuid.UUID) (*domain.Project, error)
ListByBusinessID(ctx context.Context, businessID uuid.UUID) ([]domain.Project, error)
Update(ctx context.Context, businessID, id, actorID uuid.UUID, input service.UpdateProjectInput) (*domain.Project, error)
DeleteCascade(ctx context.Context, businessID, id, actorID uuid.UUID) (deletedConversations, deletedMessages int, err error)
CountConversations(ctx context.Context, businessID, id uuid.UUID) (int, error)
}
ProjectService is the handler-facing view of the ProjectService concrete type. Declared as an interface so project_test.go can inject a mock without importing the full service package in tests. Matches the public method set of *service.ProjectService one-for-one.
Create / Update / DeleteCascade take an actorID so the service layer emits project.* audit events with the correct attribution.
type RefreshTokenResponse ¶
type RefreshTokenResponse = openapi.RefreshTokenResponse
Re-export spec-owned shapes under the historic handler.* names.
type RegistrationAccessGate ¶
type RegistrationAccessGate interface {
HasRegistrationAccess(ctx context.Context, email string) (bool, error)
}
RegistrationAccessGate is the persistence seam used by closed-beta mode. Implementations must return one opaque boolean for missing, unconsented, and not-yet-approved waitlist rows so the handler exposes no list status detail.
type ReviewHandler ¶
type ReviewHandler struct {
// contains filtered or unexported fields
}
ReviewHandler handles review-related HTTP requests
func NewReviewHandler ¶
func NewReviewHandler(reviewService ReviewService) (*ReviewHandler, error)
NewReviewHandler creates a new review handler instance
func (*ReviewHandler) BatchDraftReviews ¶
func (h *ReviewHandler) BatchDraftReviews(w http.ResponseWriter, r *http.Request)
BatchDraftReviews handles POST /businesses/{id}/reviews/batch-draft. It drafts AI replies for a bounded set of unanswered reviews (explicit ids, or the auto-selected pending backlog when none are supplied) and returns a per-item result. Each draft is one metered LLM call through the existing single-draft path, so the request is bounded to keep the credit cost predictable. Authz mirrors the manual reply path: RequireBusinessAccess + PermContentUpdate + writeLimit (router) + the service soft-delete gate.
func (*ReviewHandler) BulkApproveReviews ¶
func (h *ReviewHandler) BulkApproveReviews(w http.ResponseWriter, r *http.Request)
BulkApproveReviews handles POST /businesses/{id}/reviews/bulk-approve. It publishes the stored draft for every POSITIVE review in the request (needs_review and negatives are excluded and never published) via the same dispatch path a manual reply uses, and returns a per-item result. reviewIds is required. Authz mirrors the manual reply path.
func (*ReviewHandler) GetDelegationMetrics ¶
func (h *ReviewHandler) GetDelegationMetrics(w http.ResponseWriter, r *http.Request)
func (*ReviewHandler) GetReview ¶
func (h *ReviewHandler) GetReview(w http.ResponseWriter, r *http.Request)
GetReview handles GET /api/v1/reviews/{id}
func (*ReviewHandler) GetReviewSLA ¶
func (h *ReviewHandler) GetReviewSLA(w http.ResponseWriter, r *http.Request)
GetReviewSLA handles GET /businesses/{id}/reviews/sla. It returns aggregate-only response-SLA metrics (counts, unanswered-age buckets, median / average response time, and percent answered within a target window). No author name, review text, or reply text is fetched or returned. Authz mirrors the other GET review endpoints: RequireBusinessAccess + content:read.
func (*ReviewHandler) ListReviews ¶
func (h *ReviewHandler) ListReviews(w http.ResponseWriter, r *http.Request)
ListReviews handles GET /api/v1/reviews
func (*ReviewHandler) RefreshReviews ¶
func (h *ReviewHandler) RefreshReviews(w http.ResponseWriter, r *http.Request)
RefreshReviews handles POST /api/v1/reviews/refresh — triggers an on-demand pull from every connected platform for the caller's single business (resolved from the request's BusinessContext). Synchronous: the response returns once the dispatch completes (or fails) so the frontend can immediately invalidate its query and show the new rows. The endpoint is rate-naturally-bounded by the Cloudflare-style 60s gateway timeout combined with the syncer's own per-platform 60s per-NATS-request budget. It is a write-class action that drives external platform work, so it requires PermContentUpdate — a read-only viewer must not trigger the fanout.
func (*ReviewHandler) ReplyToReview ¶
func (h *ReviewHandler) ReplyToReview(w http.ResponseWriter, r *http.Request)
ReplyToReview handles PUT /api/v1/reviews/{id}/reply
func (*ReviewHandler) RetryReviewReply ¶
func (h *ReviewHandler) RetryReviewReply(w http.ResponseWriter, r *http.Request)
RetryReviewReply handles POST /businesses/{id}/reviews/{reviewID}/reply/retry — re-sends the stored reply of a review whose previous send failed. No body: the failed manual reply persisted the attempted text, so the retry re-uses it verbatim (and the same dispatch idempotency key, so an already-delivered reply is never posted twice). A review not in the error reply state → 409 with the stable reply_not_retryable code. Authz mirrors ReplyToReview: RequireBusinessAccess + PermContentUpdate + writeLimit (router) + the service soft-delete gate.
func (*ReviewHandler) SetSSECounter ¶
func (h *ReviewHandler) SetSSECounter(c *ssecounter.Counter, defaultTier string)
SetSSECounter wires the per-user concurrency cap (optional). Mirrors ChatProxyHandler.SetSSECounter so refresh shares the chat/resume budget.
type ReviewService ¶
type ReviewService interface {
List(ctx context.Context, businessID uuid.UUID, filter domain.ReviewFilter) ([]domain.Review, int, error)
GetByID(ctx context.Context, businessID uuid.UUID, id string) (*domain.Review, error)
Reply(ctx context.Context, businessID uuid.UUID, id string, replyText string) error
RetryReply(ctx context.Context, businessID uuid.UUID, id string) error
Refresh(ctx context.Context, userID uuid.UUID) error
BatchDraft(ctx context.Context, businessID uuid.UUID, reviewIDs []string) ([]service.BatchItemResult, error)
BulkApprove(ctx context.Context, businessID uuid.UUID, reviewIDs []string) ([]service.BatchItemResult, error)
SLA(ctx context.Context, businessID uuid.UUID, targetHours int) (service.SLAStats, error)
DelegationMetrics(ctx context.Context, businessID uuid.UUID) (service.DelegationMetrics, error)
}
ReviewService defines the interface for review operations used by handler. List/GetByID/Reply receive businessID (extracted from /businesses/{id} URL by RequireBusinessAccess middleware); Refresh resolves the same single business from the request's BusinessContext and takes userID only for logging/audit.
type RolesHandler ¶
type RolesHandler struct {
// contains filtered or unexported fields
}
RolesHandler serves the business role CRUD + MyPermissions endpoints. See docs/api/handlers/roles.md.
func NewRolesHandler ¶
func NewRolesHandler( rr domain.RoleRepository, mr domain.BusinessMembershipRepository, bs businessGetter, pool poolBeginner, inv roleCacheInvalidator, auditLogger audit.Logger, ) (*RolesHandler, error)
NewRolesHandler constructs a RolesHandler; every dep is required. businessService lets the write endpoints reject mutations against a soft-deleted (erasure-pending) organization that RequireBusinessAccess (membership-only) does not gate.
func (*RolesHandler) Create ¶
func (h *RolesHandler) Create(w http.ResponseWriter, r *http.Request)
Create handles POST /api/v1/businesses/{id}/roles (PermRolesCreate). See docs/api/handlers/roles.md.
Note: `?clone_from=` is intentionally ignored server-side — the frontend pre-fills permissions on the client and POSTs the result here.
func (*RolesHandler) Delete ¶
func (h *RolesHandler) Delete(w http.ResponseWriter, r *http.Request)
Delete handles DELETE /api/v1/businesses/{id}/roles/{roleId}?reassign_to=<uuid> (PermRolesDelete). See docs/api/handlers/roles.md.
func (*RolesHandler) List ¶
func (h *RolesHandler) List(w http.ResponseWriter, r *http.Request)
List handles GET /api/v1/businesses/{id}/roles (PermRolesRead). See docs/api/handlers/roles.md.
func (*RolesHandler) MyPermissions ¶
func (h *RolesHandler) MyPermissions(w http.ResponseWriter, r *http.Request)
MyPermissions handles GET /api/v1/businesses/{id}/me/permissions. See docs/api/handlers/roles.md.
No additional authz.Can gate — BY DESIGN. RequireBusinessAccess on the parent route already rejects non-members (404) and suspended members (403); any active member can read their own permissions.
func (*RolesHandler) Update ¶
func (h *RolesHandler) Update(w http.ResponseWriter, r *http.Request)
Update handles PATCH /api/v1/businesses/{id}/roles/{roleId} (PermRolesUpdate). See docs/api/handlers/roles.md.
type SearchHandler ¶
type SearchHandler struct {
// contains filtered or unexported fields
}
SearchHandler implements GET /api/v1/search?q=&project_id=&limit=20.
Maps the three search sentinel errors:
- domain.ErrSearchIndexNotReady → 503 + Retry-After: 5 header.
- domain.ErrInvalidScope → 500 (defense-in-depth; should not be reachable because we resolve scope server-side, but the repo guards anyway).
- other errors → 500 with metadata-only log line.
Logging contract: every log line carries {user_id, business_id, query_length}. NEVER the literal query text. NO `"query"` slog field key.
func NewSearchHandler ¶
func NewSearchHandler(searcher *service.Searcher) (*SearchHandler, error)
NewSearchHandler — searcher dep is mandatory. Pattern parallels NewConversationHandler (returns *Handler, error). Nil dep returns a nil handler + descriptive error so cmd/main.go fails at boot.
func (*SearchHandler) Search ¶
func (h *SearchHandler) Search(w http.ResponseWriter, r *http.Request)
Search handles GET /api/v1/search.
Query params:
- q (required, 2 ≤ length ≤ maxQueryLength) — search query
- project_id (optional) — UUID-shaped scope filter
- limit (optional, ≤ 50) — max rows (default 20)
Response:
- 200 OK + JSON [SearchResult ...] on success
- 400 on missing/short/over-long q
- 403 on missing permission
- 500 on missing BusinessContext (middleware misconfiguration)
- 503 + Retry-After: 5 on cold-boot before indexes are ready
- 500 on unexpected error or scope-guard surfacing (server-side bug)
type SoleOwnerEntry ¶
SoleOwnerEntry is the public shape SoleOwnerExtractor returns. Mirrors service.SoleOwnerBusiness without an import cycle.
type TelemetryHandler ¶
type TelemetryHandler struct {
// contains filtered or unexported fields
}
TelemetryHandler handles frontend telemetry ingestion.
func NewTelemetryHandler ¶
func NewTelemetryHandler(svc telemetryIngester) *TelemetryHandler
NewTelemetryHandler creates a new TelemetryHandler.
func (*TelemetryHandler) Ingest ¶
func (h *TelemetryHandler) Ingest(w http.ResponseWriter, r *http.Request)
Ingest accepts an array of frontend telemetry events and persists them with the authenticated user_id stamped server-side. The request context carries a correlation_id from CorrelationID middleware.
func (*TelemetryHandler) IngestForBusiness ¶
func (h *TelemetryHandler) IngestForBusiness(w http.ResponseWriter, r *http.Request)
IngestForBusiness accepts frontend telemetry for the authorized business in the request context. Both identifiers are stamped from server-owned context.
type TitlerHandler ¶
type TitlerHandler struct {
// contains filtered or unexported fields
}
TitlerHandler handles the auto-titler regenerate endpoint.
The titler field is typed as *service.Titler concretely: there is exactly ONE canonical mocking seam — the package-private chatCaller interface inside services/api/internal/service. Handler tests construct a real *service.Titler with a fake chatCaller via service.FakeChatCaller. NO parallel titlerCaller interface is introduced anywhere in this phase.
titler is allowed to be nil (graceful disable when TITLER_MODEL/LLM_MODEL is unset OR no LLM provider key is configured — Pitfall 1). The RegenerateTitle handler returns 503 in that case.
func NewTitlerHandler ¶
func NewTitlerHandler( titler *service.Titler, businessService BusinessService, conversationRepo domain.ConversationRepository, messageRepo domain.MessageRepository, historyLimit int, ) *TitlerHandler
NewTitlerHandler constructs a TitlerHandler. titler may be nil (graceful disable). conversationRepo and messageRepo MUST be non-nil — they are wiring-time invariants and a nil there is a programmer bug. Mirror the service/hitl.go:92-123 nil-guard convention. A non-positive historyLimit falls back to defaultHistoryLimit.
func (*TitlerHandler) RegenerateTitle ¶
func (h *TitlerHandler) RegenerateTitle(w http.ResponseWriter, r *http.Request)
RegenerateTitle handles POST /api/v1/conversations/{id}/regenerate-title.
State machine:
- middleware.GetUserID — 401 on miss. 1a. business soft-delete gate: h.businessService.GetByID — 404 on ErrBusinessNotFound (GetByID filters deleted_at IS NULL, so a soft-deleted organization reads as not-found and its conversations become unwritable), 500 on other err.
- h.conversationRepo.GetByID — 404 on ErrConversationNotFound, 500 on other err.
- ownership: conv.UserID != userID.String() → 403.
- titler-disabled gate: h.titler == nil → 503 (graceful disable).
- status check (verbatim Russian copy): - status == "manual" → 409 "Нельзя регенерировать — вы уже переименовали чат вручную" - status == "auto_pending" → 409 "Заголовок уже генерируется"
- atomic transition via TransitionToAutoPending; on ErrConversationNotFound → 409 "title_state_changed" (race window between the read in step 2 and this atomic write — manual rename arrived mid-flight).
- fetch user + assistant text; spawn titler goroutine on a fresh detached ctx with 30s timeout (r.Context() is unsafe — it's canceled at HTTP response close, the cheap-LLM call takes 3-8s).
- respond 200 (empty body) — fire-and-forget.
type TokenService ¶
type TokenService interface {
GetDecryptedToken(ctx context.Context, businessID uuid.UUID, platform, externalID, reason string) (*service.TokenResponse, error)
}
TokenService defines the interface for decrypted token retrieval
type ToolsCache ¶
ToolsCache is the narrow interface this handler needs from *service.ToolsRegistryCache. Declared locally so business_test doesn't need to import service for test-only fakes.
type UserDeletionHandler ¶
type UserDeletionHandler struct {
// contains filtered or unexported fields
}
UserDeletionHandler owns DELETE /users/me + POST /users/me/restore.
func NewUserDeletionHandler ¶
func NewUserDeletionHandler(svc AccountDeletionServiceAPI, allowedOrigins []string) *UserDeletionHandler
NewUserDeletionHandler constructs the handler. allowedOrigins holds the CORS_ALLOWED_ORIGINS values for Restore's Origin-header CSRF check.
func (*UserDeletionHandler) Delete ¶
func (h *UserDeletionHandler) Delete(w http.ResponseWriter, r *http.Request)
Delete handles DELETE /api/v1/users/me. See <api_contract> in the plan for the canonical response shapes (204 / 401 / 403 / 409 / 423).
CSRF defense: the Origin header MUST match an allowed CORS origin (mirrors Restore). The endpoint is side-effecting (schedules account deletion) on an ambient cookie/Bearer session, so a CSRF-vulnerable path could trigger deletion from a hostile origin. Origin is set by browsers on every fetch/XHR and cannot be forged by JS in the victim's session.
func (*UserDeletionHandler) Restore ¶
func (h *UserDeletionHandler) Restore(w http.ResponseWriter, r *http.Request)
Restore handles POST /api/v1/users/me/restore. See <api_contract> in the plan for the canonical response shapes (204 / 403 / 404 / 410).
CSRF defense (T-DEL-10): Origin header MUST match an allowed CORS origin. The endpoint is side-effecting (clears deletion state) with no body, so a CSRF-vulnerable cookie-only path could trigger restore from a hostile origin. Origin is set by browsers on every fetch/XHR POST and cannot be forged by JS in the victim's session.
type UserService ¶
type UserService interface {
Register(ctx context.Context, email, password string) (*domain.User, error)
// RegisterWithContext records three consents in the same tx as the user row.
RegisterWithContext(ctx context.Context, email, password string, regCtx service.RegistrationContext) (*domain.User, error)
Login(ctx context.Context, email, password string) (user *domain.User, accessToken, refreshToken string, err error)
RefreshToken(ctx context.Context, refreshToken string) (user *domain.User, accessToken, newRefreshToken string, err error)
Logout(ctx context.Context, refreshToken string) error
GetByID(ctx context.Context, userID uuid.UUID) (*domain.User, error)
ChangePassword(ctx context.Context, userID uuid.UUID, currentPassword, newPassword string) error
UpdatePreferredLocale(ctx context.Context, userID uuid.UUID, locale string) error
UpdateName(ctx context.Context, userID uuid.UUID, name string) error
}
UserService defines user-related operations.
type ValidationErrorResponse ¶
type ValidationErrorResponse = openapi.ValidationErrorResponse
Re-export spec-owned error envelopes under the historic handler.* names so the existing test suite continues to compile against named handler types. Wire shape (keys + nesting) is identical to the legacy local definitions — both `error` and `fields` are required, matching writeJSONError / writeValidationError output.
type WeeklyValueRecapHandler ¶
type WeeklyValueRecapHandler struct {
// contains filtered or unexported fields
}
func NewWeeklyValueRecapHandler ¶
func NewWeeklyValueRecapHandler(service weeklyValueRecapReader) *WeeklyValueRecapHandler
func (*WeeklyValueRecapHandler) GetLatest ¶
func (h *WeeklyValueRecapHandler) GetLatest(w http.ResponseWriter, r *http.Request)
GetLatest returns only the prior completed UTC week's row. Missing and suppressed weeks are 204, so an older row can never masquerade as current.
Source Files
¶
- agent_task.go
- audit_log.go
- auth.go
- billing.go
- business.go
- business_deletion.go
- channel_request.go
- chat_proxy.go
- consents.go
- content_template.go
- conversation.go
- error_mapping.go
- feedback.go
- helpers.go
- hitl.go
- integration.go
- interfaces.go
- internal_billing.go
- internal_token.go
- internal_yandex_shared.go
- invitations.go
- landing.go
- members.go
- permissions.go
- platforms.go
- post.go
- presence_health.go
- project.go
- response.go
- review.go
- roles.go
- search.go
- telemetry.go
- titler.go
- user_deletion.go
- weekly_value_recap.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package connect holds the paste-flow integration handlers — Telegram (Login Widget verify + bot-token connect) and VK community access-token paste.
|
Package connect holds the paste-flow integration handlers — Telegram (Login Widget verify + bot-token connect) and VK community access-token paste. |
|
Package oauth holds the true OAuth code-flow integration handlers (VK, Yandex.Business, Google Business Profile).
|
Package oauth holds the true OAuth code-flow integration handlers (VK, Yandex.Business, Google Business Profile). |