mirror of
https://github.com/we-promise/sure.git
synced 2026-05-07 21:04:12 +00:00
* feat(api): expose rule run history * fix(api): address rule run review * fix(api): complete rule run review * test(api): cover unauthenticated rule run show * test(api): align rule run api key helper * Small Sonnet nit-pick --------- Co-authored-by: Juan José Mata <jjmata@jjmata.com>
905 lines
36 KiB
Ruby
905 lines
36 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'rails_helper'
|
|
|
|
RSpec.configure do |config|
|
|
config.openapi_root = Rails.root.join('docs', 'api').to_s
|
|
|
|
config.openapi_specs = {
|
|
'openapi.yaml' => {
|
|
openapi: '3.0.3',
|
|
info: {
|
|
title: 'Sure API',
|
|
version: 'v1',
|
|
description: 'OpenAPI documentation generated from executable request specs.'
|
|
},
|
|
servers: [
|
|
{
|
|
url: 'https://app.sure.am',
|
|
description: 'Production'
|
|
},
|
|
{
|
|
url: 'http://localhost:3000',
|
|
description: 'Local development'
|
|
}
|
|
],
|
|
components: {
|
|
securitySchemes: {
|
|
apiKeyAuth: {
|
|
type: :apiKey,
|
|
name: 'X-Api-Key',
|
|
in: :header,
|
|
description: 'API key for authentication. Generate one from your account settings.'
|
|
}
|
|
},
|
|
schemas: {
|
|
Pagination: {
|
|
type: :object,
|
|
required: %w[page per_page total_count total_pages],
|
|
properties: {
|
|
page: { type: :integer, minimum: 1 },
|
|
per_page: { type: :integer, minimum: 1 },
|
|
total_count: { type: :integer, minimum: 0 },
|
|
total_pages: { type: :integer, minimum: 0 }
|
|
}
|
|
},
|
|
FamilyExportFile: {
|
|
type: :object,
|
|
required: %w[attached],
|
|
properties: {
|
|
attached: { type: :boolean },
|
|
byte_size: { type: :integer, nullable: true, minimum: 0 },
|
|
content_type: { type: :string, nullable: true }
|
|
}
|
|
},
|
|
FamilyExport: {
|
|
type: :object,
|
|
required: %w[id status filename downloadable file created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
status: { type: :string, enum: %w[pending processing completed failed] },
|
|
filename: { type: :string },
|
|
downloadable: { type: :boolean },
|
|
download_path: { type: :string, nullable: true },
|
|
file: { '$ref' => '#/components/schemas/FamilyExportFile' },
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
FamilyExportResponse: {
|
|
type: :object,
|
|
required: %w[data],
|
|
properties: {
|
|
data: { '$ref' => '#/components/schemas/FamilyExport' }
|
|
}
|
|
},
|
|
FamilyExportCollection: {
|
|
type: :object,
|
|
required: %w[data meta],
|
|
properties: {
|
|
data: {
|
|
type: :array,
|
|
maxItems: 100,
|
|
items: { '$ref' => '#/components/schemas/FamilyExport' }
|
|
},
|
|
meta: { '$ref' => '#/components/schemas/Pagination' }
|
|
}
|
|
},
|
|
ErrorResponse: {
|
|
type: :object,
|
|
required: %w[error],
|
|
properties: {
|
|
error: { type: :string },
|
|
message: { type: :string, nullable: true },
|
|
details: {
|
|
oneOf: [
|
|
{ type: :array, items: { type: :string } },
|
|
{ type: :object }
|
|
],
|
|
nullable: true
|
|
},
|
|
errors: {
|
|
type: :array,
|
|
items: { type: :string },
|
|
nullable: true,
|
|
description: 'Validation error messages (alternative to details used by trades, valuations, etc.)'
|
|
}
|
|
}
|
|
},
|
|
ErrorResponseWithImportId: {
|
|
type: :object,
|
|
required: %w[error import_id],
|
|
properties: {
|
|
error: { type: :string },
|
|
message: { type: :string, nullable: true },
|
|
import_id: {
|
|
type: :string,
|
|
format: :uuid,
|
|
description: 'Import ID preserved for retry or inspection after upload succeeds but publish fails'
|
|
}
|
|
}
|
|
},
|
|
MfaRequiredResponse: {
|
|
type: :object,
|
|
required: %w[error mfa_required],
|
|
properties: {
|
|
error: { type: :string },
|
|
mfa_required: { type: :boolean }
|
|
}
|
|
},
|
|
ToolCall: {
|
|
type: :object,
|
|
required: %w[id function_name function_arguments created_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
function_name: { type: :string },
|
|
function_arguments: { type: :object, additionalProperties: true },
|
|
function_result: { type: :object, additionalProperties: true, nullable: true },
|
|
created_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
Message: {
|
|
type: :object,
|
|
required: %w[id type role content created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
type: { type: :string, enum: %w[user_message assistant_message] },
|
|
role: { type: :string, enum: %w[user assistant] },
|
|
content: { type: :string },
|
|
model: { type: :string, nullable: true },
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' },
|
|
tool_calls: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/ToolCall' },
|
|
nullable: true
|
|
}
|
|
}
|
|
},
|
|
MessageResponse: {
|
|
allOf: [
|
|
{ '$ref' => '#/components/schemas/Message' },
|
|
{
|
|
type: :object,
|
|
required: %w[chat_id],
|
|
properties: {
|
|
chat_id: { type: :string, format: :uuid },
|
|
ai_response_status: { type: :string, enum: %w[pending complete failed], nullable: true },
|
|
ai_response_message: { type: :string, nullable: true }
|
|
}
|
|
}
|
|
]
|
|
},
|
|
ChatResource: {
|
|
type: :object,
|
|
required: %w[id title created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
title: { type: :string },
|
|
error: { type: :string, nullable: true },
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
ChatSummary: {
|
|
allOf: [
|
|
{ '$ref' => '#/components/schemas/ChatResource' },
|
|
{
|
|
type: :object,
|
|
required: %w[message_count],
|
|
properties: {
|
|
message_count: { type: :integer, minimum: 0 },
|
|
last_message_at: { type: :string, format: :'date-time', nullable: true }
|
|
}
|
|
}
|
|
]
|
|
},
|
|
ChatDetail: {
|
|
allOf: [
|
|
{ '$ref' => '#/components/schemas/ChatResource' },
|
|
{
|
|
type: :object,
|
|
required: %w[messages],
|
|
properties: {
|
|
messages: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/Message' }
|
|
},
|
|
pagination: {
|
|
'$ref' => '#/components/schemas/Pagination',
|
|
nullable: true
|
|
}
|
|
}
|
|
}
|
|
]
|
|
},
|
|
ChatCollection: {
|
|
type: :object,
|
|
required: %w[chats pagination],
|
|
properties: {
|
|
chats: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/ChatSummary' }
|
|
},
|
|
pagination: { '$ref' => '#/components/schemas/Pagination' }
|
|
}
|
|
},
|
|
RetryResponse: {
|
|
type: :object,
|
|
required: %w[message message_id],
|
|
properties: {
|
|
message: { type: :string },
|
|
message_id: { type: :string, format: :uuid }
|
|
}
|
|
},
|
|
Account: {
|
|
type: :object,
|
|
required: %w[id name account_type],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
name: { type: :string },
|
|
account_type: { type: :string, nullable: true },
|
|
status: { type: :string }
|
|
}
|
|
},
|
|
AccountDetail: {
|
|
type: :object,
|
|
required: %w[id name balance balance_cents cash_balance cash_balance_cents currency classification account_type status created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
name: { type: :string },
|
|
balance: { type: :string },
|
|
balance_cents: { type: :integer, description: 'Signed balance in minor currency units' },
|
|
cash_balance: { type: :string },
|
|
cash_balance_cents: { type: :integer, description: 'Signed cash balance in minor currency units' },
|
|
currency: { type: :string },
|
|
classification: { type: :string },
|
|
account_type: { type: :string, nullable: true },
|
|
subtype: { type: :string, nullable: true },
|
|
status: { type: :string, enum: %w[active draft disabled pending_deletion] },
|
|
institution_name: { type: :string, nullable: true },
|
|
institution_domain: { type: :string, nullable: true },
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
AccountCollection: {
|
|
type: :object,
|
|
required: %w[accounts pagination],
|
|
properties: {
|
|
accounts: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/AccountDetail' }
|
|
},
|
|
pagination: { '$ref' => '#/components/schemas/Pagination' }
|
|
}
|
|
},
|
|
FamilySettings: {
|
|
type: :object,
|
|
required: %w[id currency locale date_format month_start_day moniker default_account_sharing custom_enabled_currencies enabled_currencies created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
name: { type: :string, nullable: true },
|
|
currency: { type: :string },
|
|
locale: { type: :string },
|
|
date_format: { type: :string },
|
|
country: { type: :string, nullable: true },
|
|
timezone: { type: :string, nullable: true },
|
|
month_start_day: { type: :integer, minimum: 1, maximum: 28 },
|
|
moniker: { type: :string, enum: Family::MONIKERS },
|
|
default_account_sharing: { type: :string, enum: %w[shared private] },
|
|
custom_enabled_currencies: { type: :boolean },
|
|
enabled_currencies: {
|
|
type: :array,
|
|
items: { type: :string }
|
|
},
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
Category: {
|
|
type: :object,
|
|
required: %w[id name color icon],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
name: { type: :string },
|
|
color: { type: :string },
|
|
icon: { type: :string }
|
|
}
|
|
},
|
|
CategoryParent: {
|
|
type: :object,
|
|
required: %w[id name],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
name: { type: :string }
|
|
}
|
|
},
|
|
CategoryDetail: {
|
|
type: :object,
|
|
required: %w[id name color icon subcategories_count created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
name: { type: :string },
|
|
color: { type: :string },
|
|
icon: { type: :string },
|
|
parent: { '$ref' => '#/components/schemas/CategoryParent', nullable: true },
|
|
subcategories_count: { type: :integer, minimum: 0 },
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
CategoryCollection: {
|
|
type: :object,
|
|
required: %w[categories pagination],
|
|
properties: {
|
|
categories: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/CategoryDetail' }
|
|
},
|
|
pagination: { '$ref' => '#/components/schemas/Pagination' }
|
|
}
|
|
},
|
|
Merchant: {
|
|
type: :object,
|
|
required: %w[id name],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
name: { type: :string }
|
|
}
|
|
},
|
|
MerchantDetail: {
|
|
type: :object,
|
|
required: %w[id name type created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
name: { type: :string },
|
|
type: { type: :string, enum: %w[FamilyMerchant ProviderMerchant] },
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
Tag: {
|
|
type: :object,
|
|
required: %w[id name color],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
name: { type: :string },
|
|
color: { type: :string }
|
|
}
|
|
},
|
|
TagDetail: {
|
|
type: :object,
|
|
required: %w[id name color created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
name: { type: :string },
|
|
color: { type: :string },
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
TagCollection: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/TagDetail' }
|
|
},
|
|
RuleAction: {
|
|
type: :object,
|
|
required: %w[id action_type created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
action_type: { type: :string },
|
|
value: { type: :string, nullable: true },
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
RuleCondition: {
|
|
type: :object,
|
|
required: %w[id condition_type operator sub_conditions created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
condition_type: { type: :string },
|
|
operator: { type: :string },
|
|
value: { type: :string, nullable: true },
|
|
sub_conditions: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/RuleCondition' }
|
|
},
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
Rule: {
|
|
type: :object,
|
|
required: %w[id resource_type active conditions actions created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
name: { type: :string, nullable: true },
|
|
resource_type: { type: :string, enum: %w[transaction] },
|
|
active: { type: :boolean },
|
|
effective_date: { type: :string, format: :date, nullable: true },
|
|
conditions: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/RuleCondition' }
|
|
},
|
|
actions: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/RuleAction' }
|
|
},
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
RuleResponse: {
|
|
type: :object,
|
|
required: %w[data],
|
|
properties: {
|
|
data: { '$ref' => '#/components/schemas/Rule' }
|
|
}
|
|
},
|
|
RuleCollection: {
|
|
type: :object,
|
|
required: %w[data meta],
|
|
properties: {
|
|
data: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/Rule' }
|
|
},
|
|
meta: {
|
|
type: :object,
|
|
required: %w[current_page total_pages total_count per_page],
|
|
properties: {
|
|
current_page: { type: :integer },
|
|
next_page: { type: :integer, nullable: true },
|
|
prev_page: { type: :integer, nullable: true },
|
|
total_pages: { type: :integer },
|
|
total_count: { type: :integer },
|
|
per_page: { type: :integer }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
RuleRun: {
|
|
type: :object,
|
|
required: %w[id rule_id rule_name execution_type status transactions_queued transactions_processed transactions_modified pending_jobs_count executed_at rule created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
rule_id: { type: :string, format: :uuid },
|
|
rule_name: { type: :string, nullable: true },
|
|
execution_type: { type: :string, enum: %w[manual scheduled] },
|
|
status: { type: :string, enum: %w[pending success failed] },
|
|
transactions_queued: { type: :integer, minimum: 0 },
|
|
transactions_processed: { type: :integer, minimum: 0 },
|
|
transactions_modified: { type: :integer, minimum: 0 },
|
|
pending_jobs_count: { type: :integer, minimum: 0 },
|
|
executed_at: { type: :string, format: :'date-time' },
|
|
error_message: { type: :string, nullable: true },
|
|
rule: {
|
|
type: :object,
|
|
nullable: true,
|
|
required: %w[id resource_type active],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
name: { type: :string, nullable: true },
|
|
resource_type: { type: :string },
|
|
active: { type: :boolean }
|
|
}
|
|
},
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
RuleRunResponse: {
|
|
type: :object,
|
|
required: %w[data],
|
|
properties: {
|
|
data: { '$ref' => '#/components/schemas/RuleRun' }
|
|
}
|
|
},
|
|
RuleRunCollection: {
|
|
type: :object,
|
|
required: %w[data meta],
|
|
properties: {
|
|
data: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/RuleRun' }
|
|
},
|
|
meta: {
|
|
type: :object,
|
|
required: %w[current_page total_pages total_count per_page],
|
|
properties: {
|
|
current_page: { type: :integer },
|
|
next_page: { type: :integer, nullable: true },
|
|
prev_page: { type: :integer, nullable: true },
|
|
total_pages: { type: :integer },
|
|
total_count: { type: :integer },
|
|
per_page: { type: :integer }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
Transfer: {
|
|
type: :object,
|
|
required: %w[id amount currency],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
amount: { type: :string },
|
|
currency: { type: :string },
|
|
other_account: { '$ref' => '#/components/schemas/Account', nullable: true }
|
|
}
|
|
},
|
|
RecurringTransaction: {
|
|
type: :object,
|
|
required: %w[id amount amount_cents currency expected_day_of_month last_occurrence_date next_expected_date status occurrence_count manual created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
amount: { type: :string },
|
|
amount_cents: { type: :integer, description: 'Amount in currency minor units' },
|
|
currency: { type: :string },
|
|
expected_day_of_month: { type: :integer, minimum: 1, maximum: 31 },
|
|
last_occurrence_date: { type: :string, format: :date },
|
|
next_expected_date: { type: :string, format: :date },
|
|
status: { type: :string, enum: %w[active inactive] },
|
|
occurrence_count: { type: :integer, minimum: 0 },
|
|
name: { type: :string, nullable: true },
|
|
manual: { type: :boolean },
|
|
expected_amount_min: { type: :string, nullable: true },
|
|
expected_amount_min_cents: { type: :integer, nullable: true, description: 'Minimum expected amount in currency minor units' },
|
|
expected_amount_max: { type: :string, nullable: true },
|
|
expected_amount_max_cents: { type: :integer, nullable: true, description: 'Maximum expected amount in currency minor units' },
|
|
expected_amount_avg: { type: :string, nullable: true },
|
|
expected_amount_avg_cents: { type: :integer, nullable: true, description: 'Average expected amount in currency minor units' },
|
|
account: { '$ref' => '#/components/schemas/Account', nullable: true },
|
|
merchant: { '$ref' => '#/components/schemas/Merchant', nullable: true },
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
RecurringTransactionCollection: {
|
|
type: :object,
|
|
required: %w[recurring_transactions pagination],
|
|
properties: {
|
|
recurring_transactions: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/RecurringTransaction' }
|
|
},
|
|
pagination: { '$ref' => '#/components/schemas/Pagination' }
|
|
}
|
|
},
|
|
Transaction: {
|
|
type: :object,
|
|
required: %w[id date amount currency name classification account tags created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
date: { type: :string, format: :date },
|
|
amount: { type: :string },
|
|
currency: { type: :string },
|
|
name: { type: :string },
|
|
notes: { type: :string, nullable: true },
|
|
classification: { type: :string },
|
|
account: { '$ref' => '#/components/schemas/Account' },
|
|
category: { '$ref' => '#/components/schemas/Category', nullable: true },
|
|
merchant: { '$ref' => '#/components/schemas/Merchant', nullable: true },
|
|
tags: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/Tag' }
|
|
},
|
|
transfer: { '$ref' => '#/components/schemas/Transfer', nullable: true },
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
TransactionCollection: {
|
|
type: :object,
|
|
required: %w[transactions pagination],
|
|
properties: {
|
|
transactions: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/Transaction' }
|
|
},
|
|
pagination: { '$ref' => '#/components/schemas/Pagination' }
|
|
}
|
|
},
|
|
Valuation: {
|
|
type: :object,
|
|
required: %w[id date amount currency kind account created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
date: { type: :string, format: :date },
|
|
amount: { type: :string },
|
|
currency: { type: :string },
|
|
notes: { type: :string, nullable: true },
|
|
kind: { type: :string },
|
|
account: { '$ref' => '#/components/schemas/Account' },
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
ValuationCollection: {
|
|
type: :object,
|
|
required: %w[valuations pagination],
|
|
properties: {
|
|
valuations: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/Valuation' }
|
|
},
|
|
pagination: { '$ref' => '#/components/schemas/Pagination' }
|
|
}
|
|
},
|
|
DeleteResponse: {
|
|
type: :object,
|
|
required: %w[message],
|
|
properties: {
|
|
message: { type: :string }
|
|
}
|
|
},
|
|
ImportConfiguration: {
|
|
type: :object,
|
|
properties: {
|
|
date_col_label: { type: :string, nullable: true },
|
|
amount_col_label: { type: :string, nullable: true },
|
|
name_col_label: { type: :string, nullable: true },
|
|
category_col_label: { type: :string, nullable: true },
|
|
tags_col_label: { type: :string, nullable: true },
|
|
notes_col_label: { type: :string, nullable: true },
|
|
account_col_label: { type: :string, nullable: true },
|
|
date_format: { type: :string, nullable: true },
|
|
number_format: { type: :string, nullable: true },
|
|
signage_convention: { type: :string, nullable: true }
|
|
}
|
|
},
|
|
ImportStats: {
|
|
type: :object,
|
|
required: %w[rows_count valid_rows_count invalid_rows_count mappings_count unassigned_mappings_count],
|
|
properties: {
|
|
rows_count: { type: :integer, minimum: 0 },
|
|
valid_rows_count: { type: :integer, minimum: 0 },
|
|
invalid_rows_count: { type: :integer, minimum: 0 },
|
|
mappings_count: { type: :integer, minimum: 0 },
|
|
unassigned_mappings_count: { type: :integer, minimum: 0 }
|
|
}
|
|
},
|
|
ImportStatusSummary: {
|
|
type: :object,
|
|
required: %w[uploaded configured terminal],
|
|
properties: {
|
|
uploaded: { type: :boolean },
|
|
configured: { type: :boolean },
|
|
terminal: { type: :boolean }
|
|
}
|
|
},
|
|
ImportStatusDetail: {
|
|
allOf: [
|
|
{ '$ref' => '#/components/schemas/ImportStatusSummary' },
|
|
{
|
|
type: :object,
|
|
required: %w[cleaned publishable revertable],
|
|
properties: {
|
|
cleaned: { type: :boolean },
|
|
publishable: { type: :boolean },
|
|
revertable: { type: :boolean }
|
|
}
|
|
}
|
|
]
|
|
},
|
|
ImportSummary: {
|
|
type: :object,
|
|
required: %w[id type status created_at updated_at status_detail],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
type: { type: :string, enum: %w[TransactionImport TradeImport AccountImport MintImport CategoryImport RuleImport SureImport] },
|
|
status: { type: :string, enum: %w[pending complete importing reverting revert_failed failed] },
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' },
|
|
account_id: { type: :string, format: :uuid, nullable: true },
|
|
rows_count: { type: :integer, minimum: 0 },
|
|
error: { type: :string, nullable: true },
|
|
status_detail: { '$ref' => '#/components/schemas/ImportStatusSummary' }
|
|
}
|
|
},
|
|
ImportDetail: {
|
|
type: :object,
|
|
required: %w[id type status created_at updated_at status_detail configuration stats],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
type: { type: :string, enum: %w[TransactionImport TradeImport AccountImport MintImport CategoryImport RuleImport SureImport] },
|
|
status: { type: :string, enum: %w[pending complete importing reverting revert_failed failed] },
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' },
|
|
account_id: { type: :string, format: :uuid, nullable: true },
|
|
error: { type: :string, nullable: true },
|
|
status_detail: { '$ref' => '#/components/schemas/ImportStatusDetail' },
|
|
configuration: { '$ref' => '#/components/schemas/ImportConfiguration' },
|
|
stats: { '$ref' => '#/components/schemas/ImportStats' }
|
|
}
|
|
},
|
|
ImportCollection: {
|
|
type: :object,
|
|
required: %w[data meta],
|
|
properties: {
|
|
data: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/ImportSummary' }
|
|
},
|
|
meta: {
|
|
type: :object,
|
|
required: %w[current_page total_pages total_count per_page],
|
|
properties: {
|
|
current_page: { type: :integer, minimum: 1 },
|
|
next_page: { type: :integer, nullable: true },
|
|
prev_page: { type: :integer, nullable: true },
|
|
total_pages: { type: :integer, minimum: 0 },
|
|
total_count: { type: :integer, minimum: 0 },
|
|
per_page: { type: :integer, minimum: 1 }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
ImportResponse: {
|
|
type: :object,
|
|
required: %w[data],
|
|
properties: {
|
|
data: { '$ref' => '#/components/schemas/ImportDetail' }
|
|
}
|
|
},
|
|
Trade: {
|
|
type: :object,
|
|
required: %w[id date amount currency name qty price account created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
date: { type: :string, format: :date },
|
|
amount: { type: :string },
|
|
currency: { type: :string },
|
|
name: { type: :string },
|
|
notes: { type: :string, nullable: true },
|
|
qty: { type: :string },
|
|
price: { type: :string },
|
|
investment_activity_label: { type: :string, nullable: true },
|
|
account: { '$ref' => '#/components/schemas/Account' },
|
|
security: {
|
|
type: :object,
|
|
nullable: true,
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
ticker: { type: :string },
|
|
name: { type: :string, nullable: true }
|
|
}
|
|
},
|
|
category: {
|
|
type: :object,
|
|
nullable: true,
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
name: { type: :string }
|
|
}
|
|
},
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
TradeCollection: {
|
|
type: :object,
|
|
required: %w[trades pagination],
|
|
properties: {
|
|
trades: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/Trade' }
|
|
},
|
|
pagination: { '$ref' => '#/components/schemas/Pagination' }
|
|
}
|
|
},
|
|
Holding: {
|
|
type: :object,
|
|
required: %w[id date qty price amount currency account security created_at updated_at],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
date: { type: :string, format: :date },
|
|
qty: { type: :string, description: 'Quantity of shares held' },
|
|
price: { type: :string, description: 'Formatted price per share' },
|
|
amount: { type: :string },
|
|
currency: { type: :string },
|
|
cost_basis_source: { type: :string, nullable: true },
|
|
account: { '$ref' => '#/components/schemas/Account' },
|
|
security: {
|
|
type: :object,
|
|
required: %w[id ticker name],
|
|
properties: {
|
|
id: { type: :string, format: :uuid },
|
|
ticker: { type: :string },
|
|
name: { type: :string, nullable: true }
|
|
}
|
|
},
|
|
avg_cost: { type: :string, nullable: true },
|
|
created_at: { type: :string, format: :'date-time' },
|
|
updated_at: { type: :string, format: :'date-time' }
|
|
}
|
|
},
|
|
HoldingCollection: {
|
|
type: :object,
|
|
required: %w[holdings pagination],
|
|
properties: {
|
|
holdings: {
|
|
type: :array,
|
|
items: { '$ref' => '#/components/schemas/Holding' }
|
|
},
|
|
pagination: { '$ref' => '#/components/schemas/Pagination' }
|
|
}
|
|
},
|
|
Money: {
|
|
type: :object,
|
|
required: %w[amount currency formatted],
|
|
properties: {
|
|
amount: { type: :string, description: 'Numeric amount as string' },
|
|
currency: { type: :string, description: 'ISO 4217 currency code' },
|
|
formatted: { type: :string, description: 'Locale-formatted money string' }
|
|
}
|
|
},
|
|
BalanceSheet: {
|
|
type: :object,
|
|
required: %w[currency net_worth assets liabilities],
|
|
properties: {
|
|
currency: { type: :string, description: 'Family primary currency' },
|
|
net_worth: { '$ref' => '#/components/schemas/Money' },
|
|
assets: { '$ref' => '#/components/schemas/Money' },
|
|
liabilities: { '$ref' => '#/components/schemas/Money' }
|
|
}
|
|
},
|
|
SuccessMessage: {
|
|
type: :object,
|
|
required: %w[message],
|
|
properties: {
|
|
message: { type: :string }
|
|
}
|
|
},
|
|
ResetInitiatedResponse: {
|
|
type: :object,
|
|
required: %w[message status job_id family_id status_url],
|
|
properties: {
|
|
message: { type: :string },
|
|
status: { type: :string, enum: %w[queued] },
|
|
job_id: {
|
|
type: :string,
|
|
description: 'Informational Active Job identifier returned by the queue adapter; reset status is family-scoped, not job-scoped.'
|
|
},
|
|
family_id: { type: :string, format: :uuid, description: 'UUID of the family being reset.' },
|
|
status_url: { type: :string }
|
|
}
|
|
},
|
|
ResetStatusResponse: {
|
|
type: :object,
|
|
required: %w[status family_id reset_complete counts],
|
|
properties: {
|
|
status: {
|
|
type: :string,
|
|
enum: %w[complete data_remaining],
|
|
description: 'Counts-based family reset status at response time.'
|
|
},
|
|
family_id: { type: :string, format: :uuid, description: 'UUID of the family whose reset target counts were checked.' },
|
|
reset_complete: {
|
|
type: :boolean,
|
|
description: 'True when all reset target counts are zero at response time. This is a family data snapshot, not a durable per-job completion record.'
|
|
},
|
|
counts: {
|
|
type: :object,
|
|
required: %w[accounts categories tags merchants plaid_items imports budgets],
|
|
properties: {
|
|
accounts: { type: :integer, minimum: 0 },
|
|
categories: { type: :integer, minimum: 0 },
|
|
tags: { type: :integer, minimum: 0 },
|
|
merchants: { type: :integer, minimum: 0 },
|
|
plaid_items: { type: :integer, minimum: 0 },
|
|
imports: { type: :integer, minimum: 0 },
|
|
budgets: { type: :integer, minimum: 0 }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
config.openapi_format = :yaml
|
|
end
|