When Google refuses the stored grant (invalid_grant on refresh, or HTTP 401 from the Calendar API to the access token just refreshed) on any Calendar call - listing, choosing or pushing - the connection becomes reconnect required under the account lock: the refresh token is cleared and last_sync_error set to "Google access was revoked or expired — reconnect". The call answers 409 google_reconnect_required, and later calls answer the same without contacting Google until a reconnect or a disconnect. The chosen calendar and stored calendar event ids are kept: the pushed events are still on that calendar, so forgetEvents is not run. HTTP 429 and 5xx from the token endpoint or the Calendar API are retried at most twice with backoff, then answered 503 google_unavailable, retryable, without clearing or disconnecting anything. Add GET /api/v1/integrations/google, reporting not_connected, connected or reconnect_required with the chosen calendar and last_sync_error. Migration 0015 rebuilds integrations_google so refresh_token_ct can be cleared, with a CHECK that a row without a token carries the reason. Maestro task b8d8da5a-0450-4571-963e-b3dfead24283. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
492 lines
13 KiB
Go
492 lines
13 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: settings.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
)
|
|
|
|
const clearIntegrationsGoogleToken = `-- name: ClearIntegrationsGoogleToken :execrows
|
|
UPDATE integrations_google
|
|
SET refresh_token_ct = NULL, last_sync_error = ?, updated_at = ?
|
|
WHERE id = 1
|
|
`
|
|
|
|
type ClearIntegrationsGoogleTokenParams struct {
|
|
LastSyncError sql.NullString
|
|
UpdatedAt string
|
|
}
|
|
|
|
// Records that Google refuses the stored grant: clears the refresh token and
|
|
// sets last_sync_error, keeping the chosen calendar. Affects no row when
|
|
// nothing is connected.
|
|
func (q *Queries) ClearIntegrationsGoogleToken(ctx context.Context, arg ClearIntegrationsGoogleTokenParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, clearIntegrationsGoogleToken, arg.LastSyncError, arg.UpdatedAt)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const deleteIntegrationsGoogle = `-- name: DeleteIntegrationsGoogle :exec
|
|
DELETE FROM integrations_google
|
|
WHERE id = 1
|
|
`
|
|
|
|
func (q *Queries) DeleteIntegrationsGoogle(ctx context.Context) error {
|
|
_, err := q.db.ExecContext(ctx, deleteIntegrationsGoogle)
|
|
return err
|
|
}
|
|
|
|
const getAISettings = `-- name: GetAISettings :one
|
|
SELECT id, provider, base_url, model, temperature, system_prompt, api_key_ct, api_key_last4, key_verified_at, created_at, updated_at FROM ai_settings
|
|
WHERE id = 1
|
|
`
|
|
|
|
// Returns sql.ErrNoRows until the row exists.
|
|
func (q *Queries) GetAISettings(ctx context.Context) (AiSetting, error) {
|
|
row := q.db.QueryRowContext(ctx, getAISettings)
|
|
var i AiSetting
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Provider,
|
|
&i.BaseUrl,
|
|
&i.Model,
|
|
&i.Temperature,
|
|
&i.SystemPrompt,
|
|
&i.ApiKeyCt,
|
|
&i.ApiKeyLast4,
|
|
&i.KeyVerifiedAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getChatSettings = `-- name: GetChatSettings :one
|
|
SELECT id, webhook_url_ct, space_label, last_test_at, last_test_status, created_at, updated_at FROM chat_settings
|
|
WHERE id = 1
|
|
`
|
|
|
|
// Returns sql.ErrNoRows until the row exists.
|
|
func (q *Queries) GetChatSettings(ctx context.Context) (ChatSetting, error) {
|
|
row := q.db.QueryRowContext(ctx, getChatSettings)
|
|
var i ChatSetting
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WebhookUrlCt,
|
|
&i.SpaceLabel,
|
|
&i.LastTestAt,
|
|
&i.LastTestStatus,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getHouseholdSettings = `-- name: GetHouseholdSettings :one
|
|
SELECT id, timezone, dinner_time, reminder_time, review_time, plan_draft_dow, plan_draft_time, suggestions_dow, suggestions_time, chat_autosend_dow, chat_autosend_time, onboarding_completed_at, updates_seen_at, created_at, updated_at, updated_by FROM household_settings
|
|
WHERE id = 1
|
|
`
|
|
|
|
// Returns sql.ErrNoRows until the row exists.
|
|
func (q *Queries) GetHouseholdSettings(ctx context.Context) (HouseholdSetting, error) {
|
|
row := q.db.QueryRowContext(ctx, getHouseholdSettings)
|
|
var i HouseholdSetting
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Timezone,
|
|
&i.DinnerTime,
|
|
&i.ReminderTime,
|
|
&i.ReviewTime,
|
|
&i.PlanDraftDow,
|
|
&i.PlanDraftTime,
|
|
&i.SuggestionsDow,
|
|
&i.SuggestionsTime,
|
|
&i.ChatAutosendDow,
|
|
&i.ChatAutosendTime,
|
|
&i.OnboardingCompletedAt,
|
|
&i.UpdatesSeenAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.UpdatedBy,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getIntegrationsGoogle = `-- name: GetIntegrationsGoogle :one
|
|
SELECT id, refresh_token_ct, calendar_id, calendar_name, connected_at, last_sync_error, created_at, updated_at FROM integrations_google
|
|
WHERE id = 1
|
|
`
|
|
|
|
// Returns sql.ErrNoRows until the row exists.
|
|
func (q *Queries) GetIntegrationsGoogle(ctx context.Context) (IntegrationsGoogle, error) {
|
|
row := q.db.QueryRowContext(ctx, getIntegrationsGoogle)
|
|
var i IntegrationsGoogle
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.RefreshTokenCt,
|
|
&i.CalendarID,
|
|
&i.CalendarName,
|
|
&i.ConnectedAt,
|
|
&i.LastSyncError,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getRecipeSearchSettings = `-- name: GetRecipeSearchSettings :one
|
|
SELECT id, provider, base_url, api_key_ct, created_at, updated_at, api_key_last4 FROM recipe_search_settings
|
|
WHERE id = 1
|
|
`
|
|
|
|
// Returns sql.ErrNoRows until the row exists.
|
|
func (q *Queries) GetRecipeSearchSettings(ctx context.Context) (RecipeSearchSetting, error) {
|
|
row := q.db.QueryRowContext(ctx, getRecipeSearchSettings)
|
|
var i RecipeSearchSetting
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Provider,
|
|
&i.BaseUrl,
|
|
&i.ApiKeyCt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ApiKeyLast4,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const setIntegrationsGoogleCalendar = `-- name: SetIntegrationsGoogleCalendar :execrows
|
|
UPDATE integrations_google
|
|
SET calendar_id = ?, calendar_name = ?, updated_at = ?
|
|
WHERE id = 1
|
|
`
|
|
|
|
type SetIntegrationsGoogleCalendarParams struct {
|
|
CalendarID sql.NullString
|
|
CalendarName sql.NullString
|
|
UpdatedAt string
|
|
}
|
|
|
|
// Sets the chosen calendar; affects no row when nothing is connected.
|
|
func (q *Queries) SetIntegrationsGoogleCalendar(ctx context.Context, arg SetIntegrationsGoogleCalendarParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, setIntegrationsGoogleCalendar, arg.CalendarID, arg.CalendarName, arg.UpdatedAt)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const upsertAISettings = `-- name: UpsertAISettings :one
|
|
INSERT INTO ai_settings (
|
|
id, provider, base_url, model, temperature, system_prompt, api_key_ct,
|
|
api_key_last4, key_verified_at, created_at, updated_at
|
|
) VALUES (
|
|
1, ?, ?, ?, ?, ?, ?,
|
|
?, ?, ?, ?
|
|
)
|
|
ON CONFLICT (id) DO UPDATE SET
|
|
provider = excluded.provider,
|
|
base_url = excluded.base_url,
|
|
model = excluded.model,
|
|
temperature = excluded.temperature,
|
|
system_prompt = excluded.system_prompt,
|
|
api_key_ct = excluded.api_key_ct,
|
|
api_key_last4 = excluded.api_key_last4,
|
|
key_verified_at = excluded.key_verified_at,
|
|
updated_at = excluded.updated_at
|
|
RETURNING id, provider, base_url, model, temperature, system_prompt, api_key_ct, api_key_last4, key_verified_at, created_at, updated_at
|
|
`
|
|
|
|
type UpsertAISettingsParams struct {
|
|
Provider string
|
|
BaseUrl string
|
|
Model string
|
|
Temperature float64
|
|
SystemPrompt sql.NullString
|
|
ApiKeyCt []byte
|
|
ApiKeyLast4 sql.NullString
|
|
KeyVerifiedAt sql.NullString
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
// Creates the single row (id 1) or replaces every column except id and
|
|
// created_at.
|
|
func (q *Queries) UpsertAISettings(ctx context.Context, arg UpsertAISettingsParams) (AiSetting, error) {
|
|
row := q.db.QueryRowContext(ctx, upsertAISettings,
|
|
arg.Provider,
|
|
arg.BaseUrl,
|
|
arg.Model,
|
|
arg.Temperature,
|
|
arg.SystemPrompt,
|
|
arg.ApiKeyCt,
|
|
arg.ApiKeyLast4,
|
|
arg.KeyVerifiedAt,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
var i AiSetting
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Provider,
|
|
&i.BaseUrl,
|
|
&i.Model,
|
|
&i.Temperature,
|
|
&i.SystemPrompt,
|
|
&i.ApiKeyCt,
|
|
&i.ApiKeyLast4,
|
|
&i.KeyVerifiedAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const upsertChatSettings = `-- name: UpsertChatSettings :one
|
|
INSERT INTO chat_settings (
|
|
id, webhook_url_ct, space_label, last_test_at, last_test_status,
|
|
created_at, updated_at
|
|
) VALUES (
|
|
1, ?, ?, ?, ?,
|
|
?, ?
|
|
)
|
|
ON CONFLICT (id) DO UPDATE SET
|
|
webhook_url_ct = excluded.webhook_url_ct,
|
|
space_label = excluded.space_label,
|
|
last_test_at = excluded.last_test_at,
|
|
last_test_status = excluded.last_test_status,
|
|
updated_at = excluded.updated_at
|
|
RETURNING id, webhook_url_ct, space_label, last_test_at, last_test_status, created_at, updated_at
|
|
`
|
|
|
|
type UpsertChatSettingsParams struct {
|
|
WebhookUrlCt []byte
|
|
SpaceLabel sql.NullString
|
|
LastTestAt sql.NullString
|
|
LastTestStatus sql.NullString
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
// Creates the single row (id 1) or replaces every column except id and
|
|
// created_at.
|
|
func (q *Queries) UpsertChatSettings(ctx context.Context, arg UpsertChatSettingsParams) (ChatSetting, error) {
|
|
row := q.db.QueryRowContext(ctx, upsertChatSettings,
|
|
arg.WebhookUrlCt,
|
|
arg.SpaceLabel,
|
|
arg.LastTestAt,
|
|
arg.LastTestStatus,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
var i ChatSetting
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WebhookUrlCt,
|
|
&i.SpaceLabel,
|
|
&i.LastTestAt,
|
|
&i.LastTestStatus,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const upsertHouseholdSettings = `-- name: UpsertHouseholdSettings :one
|
|
INSERT INTO household_settings (
|
|
id, timezone, dinner_time, reminder_time, review_time, plan_draft_dow,
|
|
plan_draft_time, suggestions_dow, suggestions_time, chat_autosend_dow,
|
|
chat_autosend_time, onboarding_completed_at, updates_seen_at,
|
|
updated_by, created_at, updated_at
|
|
) VALUES (
|
|
1, ?, ?, ?, ?, ?,
|
|
?, ?, ?, ?,
|
|
?, ?, ?,
|
|
?, ?, ?
|
|
)
|
|
ON CONFLICT (id) DO UPDATE SET
|
|
timezone = excluded.timezone,
|
|
dinner_time = excluded.dinner_time,
|
|
reminder_time = excluded.reminder_time,
|
|
review_time = excluded.review_time,
|
|
plan_draft_dow = excluded.plan_draft_dow,
|
|
plan_draft_time = excluded.plan_draft_time,
|
|
suggestions_dow = excluded.suggestions_dow,
|
|
suggestions_time = excluded.suggestions_time,
|
|
chat_autosend_dow = excluded.chat_autosend_dow,
|
|
chat_autosend_time = excluded.chat_autosend_time,
|
|
onboarding_completed_at = excluded.onboarding_completed_at,
|
|
updates_seen_at = excluded.updates_seen_at,
|
|
updated_by = excluded.updated_by,
|
|
updated_at = excluded.updated_at
|
|
RETURNING id, timezone, dinner_time, reminder_time, review_time, plan_draft_dow, plan_draft_time, suggestions_dow, suggestions_time, chat_autosend_dow, chat_autosend_time, onboarding_completed_at, updates_seen_at, created_at, updated_at, updated_by
|
|
`
|
|
|
|
type UpsertHouseholdSettingsParams struct {
|
|
Timezone string
|
|
DinnerTime string
|
|
ReminderTime string
|
|
ReviewTime string
|
|
PlanDraftDow int64
|
|
PlanDraftTime string
|
|
SuggestionsDow int64
|
|
SuggestionsTime string
|
|
ChatAutosendDow int64
|
|
ChatAutosendTime string
|
|
OnboardingCompletedAt sql.NullString
|
|
UpdatesSeenAt sql.NullString
|
|
UpdatedBy sql.NullString
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
// Creates the single row (id 1) or replaces every column except id and
|
|
// created_at.
|
|
func (q *Queries) UpsertHouseholdSettings(ctx context.Context, arg UpsertHouseholdSettingsParams) (HouseholdSetting, error) {
|
|
row := q.db.QueryRowContext(ctx, upsertHouseholdSettings,
|
|
arg.Timezone,
|
|
arg.DinnerTime,
|
|
arg.ReminderTime,
|
|
arg.ReviewTime,
|
|
arg.PlanDraftDow,
|
|
arg.PlanDraftTime,
|
|
arg.SuggestionsDow,
|
|
arg.SuggestionsTime,
|
|
arg.ChatAutosendDow,
|
|
arg.ChatAutosendTime,
|
|
arg.OnboardingCompletedAt,
|
|
arg.UpdatesSeenAt,
|
|
arg.UpdatedBy,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
var i HouseholdSetting
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Timezone,
|
|
&i.DinnerTime,
|
|
&i.ReminderTime,
|
|
&i.ReviewTime,
|
|
&i.PlanDraftDow,
|
|
&i.PlanDraftTime,
|
|
&i.SuggestionsDow,
|
|
&i.SuggestionsTime,
|
|
&i.ChatAutosendDow,
|
|
&i.ChatAutosendTime,
|
|
&i.OnboardingCompletedAt,
|
|
&i.UpdatesSeenAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.UpdatedBy,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const upsertIntegrationsGoogle = `-- name: UpsertIntegrationsGoogle :one
|
|
INSERT INTO integrations_google (
|
|
id, refresh_token_ct, calendar_id, calendar_name, connected_at,
|
|
last_sync_error, created_at, updated_at
|
|
) VALUES (
|
|
1, ?, ?, ?, ?,
|
|
?, ?, ?
|
|
)
|
|
ON CONFLICT (id) DO UPDATE SET
|
|
refresh_token_ct = excluded.refresh_token_ct,
|
|
calendar_id = excluded.calendar_id,
|
|
calendar_name = excluded.calendar_name,
|
|
connected_at = excluded.connected_at,
|
|
last_sync_error = excluded.last_sync_error,
|
|
updated_at = excluded.updated_at
|
|
RETURNING id, refresh_token_ct, calendar_id, calendar_name, connected_at, last_sync_error, created_at, updated_at
|
|
`
|
|
|
|
type UpsertIntegrationsGoogleParams struct {
|
|
RefreshTokenCt []byte
|
|
CalendarID sql.NullString
|
|
CalendarName sql.NullString
|
|
ConnectedAt string
|
|
LastSyncError sql.NullString
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
// Creates the single row (id 1) or replaces every column except id and
|
|
// created_at.
|
|
func (q *Queries) UpsertIntegrationsGoogle(ctx context.Context, arg UpsertIntegrationsGoogleParams) (IntegrationsGoogle, error) {
|
|
row := q.db.QueryRowContext(ctx, upsertIntegrationsGoogle,
|
|
arg.RefreshTokenCt,
|
|
arg.CalendarID,
|
|
arg.CalendarName,
|
|
arg.ConnectedAt,
|
|
arg.LastSyncError,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
var i IntegrationsGoogle
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.RefreshTokenCt,
|
|
&i.CalendarID,
|
|
&i.CalendarName,
|
|
&i.ConnectedAt,
|
|
&i.LastSyncError,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const upsertRecipeSearchSettings = `-- name: UpsertRecipeSearchSettings :one
|
|
INSERT INTO recipe_search_settings (
|
|
id, provider, base_url, api_key_ct, api_key_last4, created_at, updated_at
|
|
) VALUES (
|
|
1, ?, ?, ?, ?, ?, ?
|
|
)
|
|
ON CONFLICT (id) DO UPDATE SET
|
|
provider = excluded.provider,
|
|
base_url = excluded.base_url,
|
|
api_key_ct = excluded.api_key_ct,
|
|
api_key_last4 = excluded.api_key_last4,
|
|
updated_at = excluded.updated_at
|
|
RETURNING id, provider, base_url, api_key_ct, created_at, updated_at, api_key_last4
|
|
`
|
|
|
|
type UpsertRecipeSearchSettingsParams struct {
|
|
Provider string
|
|
BaseUrl sql.NullString
|
|
ApiKeyCt []byte
|
|
ApiKeyLast4 sql.NullString
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
// Creates the single row (id 1) or replaces every column except id and
|
|
// created_at.
|
|
func (q *Queries) UpsertRecipeSearchSettings(ctx context.Context, arg UpsertRecipeSearchSettingsParams) (RecipeSearchSetting, error) {
|
|
row := q.db.QueryRowContext(ctx, upsertRecipeSearchSettings,
|
|
arg.Provider,
|
|
arg.BaseUrl,
|
|
arg.ApiKeyCt,
|
|
arg.ApiKeyLast4,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
var i RecipeSearchSetting
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Provider,
|
|
&i.BaseUrl,
|
|
&i.ApiKeyCt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.ApiKeyLast4,
|
|
)
|
|
return i, err
|
|
}
|