mirror of
https://github.com/we-promise/sure.git
synced 2026-05-12 23:25:00 +00:00
Add full CRUD support for merchants in the API
Expand the merchants API from read-only (index/show) to full CRUD with create, update, and destroy actions. Uses API key auth with proper scope authorization (read for index/show, read_write for create/update/destroy) and family-based isolation. - Add create/update/destroy actions to MerchantsController - Update routes to include all CRUD actions - Enrich merchant JSON response with color, logo_url, website_url fields - Fix FamilyMerchant#set_default_color to only set when blank (was unconditionally overriding color on every validation) - Rewrite tests to use API key auth pattern with read/read_write scopes - Add rswag OpenAPI spec and regenerate docs - Add MerchantDetail/MerchantCollection schemas to swagger_helper https://claude.ai/code/session_01G39SUd6QEv5nUusPvjFhmh
This commit is contained in:
@@ -2,28 +2,14 @@
|
||||
|
||||
module Api
|
||||
module V1
|
||||
# API v1 endpoint for merchants
|
||||
# Provides read-only access to family and provider merchants
|
||||
#
|
||||
# @example List all merchants
|
||||
# GET /api/v1/merchants
|
||||
#
|
||||
# @example Get a specific merchant
|
||||
# GET /api/v1/merchants/:id
|
||||
#
|
||||
class MerchantsController < BaseController
|
||||
before_action -> { authorize_scope!(:read) }
|
||||
before_action -> { authorize_scope!(:read) }, only: %i[index show]
|
||||
before_action -> { authorize_scope!(:read_write) }, only: %i[create update destroy]
|
||||
before_action :set_merchant, only: %i[show update destroy]
|
||||
|
||||
# List all merchants available to the family
|
||||
#
|
||||
# Returns both family-owned merchants and provider merchants
|
||||
# that are assigned to the family's transactions.
|
||||
#
|
||||
# @return [Array<Hash>] JSON array of merchant objects
|
||||
def index
|
||||
family = current_resource_owner.family
|
||||
|
||||
# Single query with OR conditions - more efficient than Ruby deduplication
|
||||
family_merchant_ids = family.merchants.select(:id)
|
||||
provider_merchant_ids = family.transactions.select(:merchant_id)
|
||||
|
||||
@@ -34,50 +20,59 @@ module Api
|
||||
.alphabetically
|
||||
|
||||
render json: @merchants.map { |m| merchant_json(m) }
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("API Merchants Error: #{e.message}")
|
||||
render json: { error: "Failed to fetch merchants" }, status: :internal_server_error
|
||||
end
|
||||
|
||||
# Get a specific merchant by ID
|
||||
#
|
||||
# Returns a merchant if it belongs to the family or is assigned
|
||||
# to any of the family's transactions.
|
||||
#
|
||||
# @param id [String] The merchant ID
|
||||
# @return [Hash] JSON merchant object or error
|
||||
def show
|
||||
render json: merchant_json(@merchant)
|
||||
end
|
||||
|
||||
def create
|
||||
family = current_resource_owner.family
|
||||
@merchant = family.merchants.new(merchant_params)
|
||||
|
||||
@merchant = family.merchants.find_by(id: params[:id]) ||
|
||||
Merchant.joins(transactions: :entry)
|
||||
.where(entries: { account_id: family.accounts.select(:id) })
|
||||
.distinct
|
||||
.find_by(id: params[:id])
|
||||
if @merchant.save
|
||||
render json: merchant_json(@merchant), status: :created
|
||||
else
|
||||
render json: { error: @merchant.errors.full_messages.join(", ") }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
if @merchant
|
||||
def update
|
||||
if @merchant.update(merchant_params)
|
||||
render json: merchant_json(@merchant)
|
||||
else
|
||||
render json: { error: "Merchant not found" }, status: :not_found
|
||||
render json: { error: @merchant.errors.full_messages.join(", ") }, status: :unprocessable_entity
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("API Merchant Show Error: #{e.message}")
|
||||
render json: { error: "Failed to fetch merchant" }, status: :internal_server_error
|
||||
end
|
||||
|
||||
def destroy
|
||||
@merchant.destroy!
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Serialize a merchant to JSON format
|
||||
#
|
||||
# @param merchant [Merchant] The merchant to serialize
|
||||
# @return [Hash] JSON-serializable hash
|
||||
def set_merchant
|
||||
family = current_resource_owner.family
|
||||
@merchant = family.merchants.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render json: { error: "Merchant not found" }, status: :not_found
|
||||
end
|
||||
|
||||
def merchant_params
|
||||
params.require(:merchant).permit(:name, :color, :website_url)
|
||||
end
|
||||
|
||||
def merchant_json(merchant)
|
||||
{
|
||||
id: merchant.id,
|
||||
name: merchant.name,
|
||||
type: merchant.type,
|
||||
created_at: merchant.created_at,
|
||||
updated_at: merchant.updated_at
|
||||
color: merchant.color,
|
||||
logo_url: merchant.logo_url,
|
||||
website_url: merchant.website_url,
|
||||
created_at: merchant.created_at.iso8601,
|
||||
updated_at: merchant.updated_at.iso8601
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user