├── log └── .keep ├── app ├── mailers │ └── .keep ├── models │ ├── concerns │ │ └── .keep │ ├── webhook_event.rb │ ├── client_application_partner.rb │ ├── client_application.rb │ ├── user.rb │ └── webhook_delivery.rb ├── assets │ ├── stylesheets │ │ ├── help.css │ │ ├── pages.css │ │ ├── client_applications.css │ │ ├── client_application_partners.css │ │ ├── playground.css │ │ ├── v1 │ │ │ └── start.css │ │ ├── v2 │ │ │ └── start.css │ │ ├── welcome.css │ │ ├── webhook_deliveries.css │ │ ├── application.scss │ │ └── bootstrap_and_overrides.css │ ├── images │ │ ├── logo.png │ │ ├── bird_blue_16.png │ │ └── blacktocat-16.png │ ├── config │ │ └── manifest.js │ └── javascripts │ │ ├── application.js │ │ ├── msie.js │ │ └── playground.js.coffee ├── views │ ├── pages │ │ └── show.html.haml │ ├── content │ │ ├── docs.md │ │ ├── v1 │ │ │ ├── terms.md │ │ │ └── index.md │ │ └── v2 │ │ │ ├── docs │ │ │ ├── docs.md │ │ │ ├── rate_limiting.md │ │ │ ├── status.md │ │ │ ├── editions.md │ │ │ ├── images.md │ │ │ ├── bids.md │ │ │ ├── bidder_positions.md │ │ │ ├── markdown.md │ │ │ ├── fairs.md │ │ │ ├── profiles.md │ │ │ ├── sales.md │ │ │ ├── errors.md │ │ │ ├── links.md │ │ │ ├── users.md │ │ │ ├── user_details.md │ │ │ ├── genes.md │ │ │ ├── shows.md │ │ │ ├── partners.md │ │ │ ├── bidders.md │ │ │ ├── applications.md │ │ │ ├── pagination.md │ │ │ ├── collections.md │ │ │ ├── artworks.md │ │ │ ├── collection_items.md │ │ │ ├── search.md │ │ │ ├── artists.md │ │ │ └── http.md │ │ │ └── index.md │ ├── shared │ │ ├── _ga.html.haml │ │ ├── _header.html.haml │ │ ├── _footer.html.haml │ │ └── _nav.html.haml │ ├── layouts │ │ └── application.html.haml │ ├── playground │ │ └── index.html.haml │ ├── help │ │ └── show.html.haml │ ├── welcome │ │ └── index.html.haml │ ├── client_applications │ │ ├── index.html.haml │ │ ├── edit.html.haml │ │ └── show.html.haml │ ├── webhook_deliveries │ │ ├── show.html.haml │ │ └── index.html.haml │ ├── client_application_partners │ │ └── index.html.haml │ └── v1 │ │ └── start │ │ └── show.html.haml ├── controllers │ ├── help_controller.rb │ ├── health_controller.rb │ ├── welcome_controller.rb │ ├── concerns │ │ └── paginatable.rb │ ├── playground_controller.rb │ ├── application_controller.rb │ ├── v1 │ │ └── start_controller.rb │ ├── client_application_partners_controller.rb │ ├── client_applications_controller.rb │ ├── webhook_deliveries_controller.rb │ └── pages_controller.rb ├── helpers │ ├── cache_helper.rb │ ├── application_helper.rb │ ├── artsy_api │ │ ├── v1.rb │ │ └── v2.rb │ ├── open_redirect_helper.rb │ └── markdown_helper.rb └── services │ ├── client_application_partner_service.rb │ └── client_application_service.rb ├── lib ├── assets │ └── .keep ├── tasks │ └── .keep ├── templates │ └── haml │ │ └── scaffold │ │ └── _form.html.haml └── gravity.rb ├── .prettierignore ├── Procfile ├── .tool-versions ├── .foreman ├── .rspec ├── config ├── initializers │ ├── ga.rb │ ├── sentry.rb │ ├── session_store.rb │ ├── mime_types.rb │ ├── artsy_auth.rb │ ├── application_controller_renderer.rb │ ├── cookies_serializer.rb │ ├── wrap_parameters.rb │ ├── backtrace_silencers.rb │ ├── hyperclient.rb │ ├── filter_parameter_logging.rb │ ├── datadog.rb │ ├── inflections.rb │ ├── assets.rb │ └── content_security_policy.rb ├── spring.rb ├── environment.rb ├── boot.rb ├── gravity.yml ├── puma.config ├── locales │ ├── en.bootstrap.yml │ ├── simple_form.en.yml │ └── en.yml ├── secrets.yml ├── application.rb ├── routes.rb ├── puma.rb └── environments │ ├── development.rb │ ├── test.rb │ ├── staging.rb │ └── production.rb ├── hokusai ├── templates │ └── docker-compose-service.yml.j2 ├── config.yml ├── build.yml ├── test.yml ├── development.yml ├── staging.yml └── production.yml ├── public ├── favicon.ico ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── .env.example ├── .gitignore ├── vendor └── assets │ ├── stylesheets │ └── swagger-ui-dist.css │ ├── swagger-ui-dist │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── swagger-ui.css.map │ ├── absolute-path.js │ ├── index.js │ ├── README.md │ ├── index.html │ ├── oauth2-redirect.html │ └── package.json │ └── javascripts │ └── swagger-ui-dist.js ├── yarn.lock ├── bin ├── rake ├── bundle ├── rails ├── yarn ├── update └── setup ├── spec ├── support │ ├── capybara.rb │ ├── rails.rb │ └── omniauth.rb ├── features │ ├── help_spec.rb │ ├── v1 │ │ ├── terms_spec.rb │ │ ├── playground_spec.rb │ │ └── index_spec.rb │ ├── v2 │ │ ├── terms_spec.rb │ │ ├── playground_spec.rb │ │ ├── index_spec.rb │ │ ├── authentication_spec.rb │ │ └── resources │ │ │ ├── applications_spec.rb │ │ │ └── status_spec.rb │ ├── docs_spec.rb │ ├── welcome_spec.rb │ └── client_applications_spec.rb ├── spec_helper.rb ├── controllers │ └── health_controller_spec.rb ├── requests │ ├── v2 │ │ └── playground_spec.rb │ ├── docs │ │ └── resources │ │ │ └── applications_spec.rb │ └── client_applications_spec.rb └── helpers │ └── markdown_helper_spec.rb ├── renovate.json ├── .rubocop.yml ├── .vscode └── settings.json ├── config.ru ├── .standard.yml ├── scripts ├── load_secrets_and_run.sh └── ci.sh ├── Rakefile ├── .github ├── workflows │ └── run-conventional-commits-check.yml └── dependabot.yml ├── DEV.md ├── .dockerignore ├── Dockerfile ├── Gemfile ├── Guardfile ├── DEPLOYING.md ├── LICENSE ├── .circleci └── config.yml ├── README.md └── CONTRIBUTING.md /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | hokusai/*.yml 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec puma 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | ruby 3.3.0 2 | nodejs 18.20.0 3 | -------------------------------------------------------------------------------- /.foreman: -------------------------------------------------------------------------------- 1 | env: .env.shared,.env 2 | port: 3000 3 | -------------------------------------------------------------------------------- /app/assets/stylesheets/help.css: -------------------------------------------------------------------------------- 1 | /* placeholder */ 2 | -------------------------------------------------------------------------------- /app/assets/stylesheets/pages.css: -------------------------------------------------------------------------------- 1 | /* placeholder */ 2 | -------------------------------------------------------------------------------- /app/views/pages/show.html.haml: -------------------------------------------------------------------------------- 1 | %div 2 | != @content 3 | -------------------------------------------------------------------------------- /app/assets/stylesheets/client_applications.css: -------------------------------------------------------------------------------- 1 | /* placeholder */ 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | --format documentation 4 | -------------------------------------------------------------------------------- /config/initializers/ga.rb: -------------------------------------------------------------------------------- 1 | GA.tracker = ENV["GOOGLE_ANALYTICS_UA"] 2 | -------------------------------------------------------------------------------- /app/assets/stylesheets/client_application_partners.css: -------------------------------------------------------------------------------- 1 | /* placeholder */ 2 | -------------------------------------------------------------------------------- /hokusai/templates/docker-compose-service.yml.j2: -------------------------------------------------------------------------------- 1 | build: 2 | context: ../ 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artsy/doppler/main/public/favicon.ico -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Place local overrides in .env and shared configuration in .env.shared. 2 | -------------------------------------------------------------------------------- /app/assets/stylesheets/playground.css: -------------------------------------------------------------------------------- 1 | /* 2 | *= require swagger-ui-dist 3 | */ 4 | 5 | -------------------------------------------------------------------------------- /app/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artsy/doppler/main/app/assets/images/logo.png -------------------------------------------------------------------------------- /app/views/content/docs.md: -------------------------------------------------------------------------------- 1 | ## Documentation 2 | 3 | * [Public API](/v2/) 4 | * [Partner API](/v1/) 5 | -------------------------------------------------------------------------------- /config/initializers/sentry.rb: -------------------------------------------------------------------------------- 1 | Sentry.init do |config| 2 | config.dsn = ENV["SENTRY_DSN"] 3 | end 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle 2 | /log/*.log 3 | /tmp 4 | .env* 5 | !.env.example 6 | public/assets 7 | .ruby-version 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/v1/start.css: -------------------------------------------------------------------------------- 1 | /* placeholder */ 2 | 3 | .form-group { 4 | padding-top: 10px; 5 | } 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/v2/start.css: -------------------------------------------------------------------------------- 1 | /* placeholder */ 2 | 3 | .form-group { 4 | padding-top: 10px; 5 | } 6 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/swagger-ui-dist.css: -------------------------------------------------------------------------------- 1 | /* 2 | *=require swagger-ui-dist/swagger-ui.css 3 | */ 4 | 5 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/assets/images/bird_blue_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artsy/doppler/main/app/assets/images/bird_blue_16.png -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /app/assets/images/blacktocat-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artsy/doppler/main/app/assets/images/blacktocat-16.png -------------------------------------------------------------------------------- /hokusai/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | project-name: doppler 3 | template-config-files: 4 | - s3://artsy-citadel/k8s/hokusai-vars.yml 5 | -------------------------------------------------------------------------------- /spec/support/capybara.rb: -------------------------------------------------------------------------------- 1 | require "capybara/rspec" 2 | require "capybara/rails" 3 | 4 | Capybara.default_driver = :selenium 5 | -------------------------------------------------------------------------------- /app/views/shared/_ga.html.haml: -------------------------------------------------------------------------------- 1 | - if Rails.env.development? 2 | = analytics_init local: true 3 | - else 4 | = analytics_init 5 | -------------------------------------------------------------------------------- /hokusai/build.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '2' 3 | services: 4 | doppler: 5 | {% include 'templates/docker-compose-service.yml.j2' %} 6 | -------------------------------------------------------------------------------- /vendor/assets/swagger-ui-dist/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artsy/doppler/main/vendor/assets/swagger-ui-dist/favicon-16x16.png -------------------------------------------------------------------------------- /vendor/assets/swagger-ui-dist/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artsy/doppler/main/vendor/assets/swagger-ui-dist/favicon-32x32.png -------------------------------------------------------------------------------- /vendor/assets/swagger-ui-dist/swagger-ui.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"swagger-ui.css","sourceRoot":""} -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 3 | load Gem.bin_path("bundler", "bundle") 4 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w[ 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ].each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /spec/support/rails.rb: -------------------------------------------------------------------------------- 1 | require "rspec/rails" 2 | 3 | RSpec.configure do |config| 4 | config.before do 5 | Rails.cache.clear 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@artsy:app", "schedule:nonOfficeHours"], 3 | "assignees": ["joeyAghion"], 4 | "reviewers": ["mzikherman"] 5 | } 6 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - standard 3 | - standard-custom 4 | 5 | inherit_gem: 6 | standard: config/base.yml 7 | standard-custom: config/base.yml 8 | -------------------------------------------------------------------------------- /app/models/webhook_event.rb: -------------------------------------------------------------------------------- 1 | class WebhookEvent 2 | include ActiveModel::Model 3 | 4 | attr_accessor :id, :name, :created_at, :data, :partner_id 5 | end 6 | -------------------------------------------------------------------------------- /hokusai/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '2' 3 | services: 4 | doppler: 5 | command: ./scripts/ci.sh 6 | {% include 'templates/docker-compose-service.yml.j2' %} 7 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/swagger-ui-dist.js: -------------------------------------------------------------------------------- 1 | //= require swagger-ui-dist/swagger-ui-bundle.js 2 | //= require swagger-ui-dist/swagger-ui-standalone-preset.js 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "[ruby]": { 4 | "editor.defaultFormatter": "testdouble.vscode-standard-ruby" 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | //= link application.css 5 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /app/controllers/help_controller.rb: -------------------------------------------------------------------------------- 1 | class HelpController < ApplicationController 2 | skip_before_action :require_artsy_authentication 3 | 4 | def show 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path("../config/environment", __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: "_doppler_session" 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | //= require jquery 2 | //= require jquery_ujs 3 | //= require msie 4 | //= require twitter/bootstrap 5 | //= require twitter/bootstrap/rails/confirm 6 | -------------------------------------------------------------------------------- /spec/features/help_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Help" do 4 | it "displays help" do 5 | visit "/help" 6 | expect(page.body).to include "Help" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /spec/support/omniauth.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before :all do 3 | # use mock oauth responses and automatic redirects for 3rd parties 4 | OmniAuth.config.test_mode = true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/views/shared/_header.html.haml: -------------------------------------------------------------------------------- 1 | %head 2 | %title Artsy APIs 3 | = stylesheet_link_tag 'application', media: 'all' 4 | = stylesheet_link_tag params[:controller] 5 | = csrf_meta_tags 6 | = render 'shared/ga' 7 | -------------------------------------------------------------------------------- /app/controllers/health_controller.rb: -------------------------------------------------------------------------------- 1 | class HealthController < ApplicationController 2 | skip_before_action :require_artsy_authentication, only: [:ping] 3 | 4 | def ping 5 | render json: {status: :ok} 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | # require 'bootsnap/setup' # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /spec/features/v1/terms_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Partner API Terms" do 4 | it "displays terms of use" do 5 | visit "/v1/terms" 6 | expect(page.body).to include "PARTNER API TERMS OF USE" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/features/v2/terms_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Public API Terms" do 4 | it "displays terms of use" do 5 | visit "/v2/terms" 6 | expect(page.body).to include "PUBLIC API TERMS OF USE" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/models/client_application_partner.rb: -------------------------------------------------------------------------------- 1 | class ClientApplicationPartner 2 | include ActiveModel::Model 3 | 4 | attr_accessor :id, :client_application_name, :client_application_id, :partner_id, :partner_name, :created_at, :updated_at 5 | end 6 | -------------------------------------------------------------------------------- /app/views/content/v1/terms.md: -------------------------------------------------------------------------------- 1 | #PARTNER API TERMS OF USE 2 | 3 | You may not use the partner API without explicit, written permission from Artsy. 4 | 5 | Please contact [support@artsy.net](mailto:support@artsy.net) to discuss partner API access. 6 | -------------------------------------------------------------------------------- /config/initializers/artsy_auth.rb: -------------------------------------------------------------------------------- 1 | ArtsyAuth.configure do |config| 2 | config.artsy_api_url = ENV["ARTSY_API_URL"] 3 | config.application_id = ENV["ARTSY_API_CLIENT_ID"] 4 | config.application_secret = ENV["ARTSY_API_CLIENT_SECRET"] 5 | end 6 | -------------------------------------------------------------------------------- /hokusai/development.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '2' 3 | services: 4 | doppler: 5 | env_file: ../.env 6 | {% include 'templates/docker-compose-service.yml.j2' %} 7 | ports: 8 | - 8080:8080 9 | volumes: 10 | - ../:/app 11 | -------------------------------------------------------------------------------- /.standard.yml: -------------------------------------------------------------------------------- 1 | format: progress 2 | 3 | rules: 4 | Standard/SemanticBlocks: disable 5 | 6 | # TODO: These can be removed when upgrading to Ruby 3 7 | ignore: 8 | - "app/controllers/pages_controller.rb" 9 | - "app/controllers/welcome_controller.rb" 10 | 11 | -------------------------------------------------------------------------------- /scripts/load_secrets_and_run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CMD="$@" 4 | 5 | if [ ! -z "$SECRETS_FILE" ] 6 | then 7 | echo "SECRETS_FILE env var is defined. Sourcing secrets file..." 8 | source "$SECRETS_FILE" 9 | fi 10 | 11 | echo "Running command: $CMD" 12 | $CMD 13 | -------------------------------------------------------------------------------- /spec/features/v1/playground_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Playground" do 4 | it "requires authentication" do 5 | expect_any_instance_of(ApplicationController).to receive(:require_artsy_authentication) 6 | visit "/v1/playground" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require File.expand_path("config/application", __dir__) 2 | 3 | Rails.application.load_tasks 4 | 5 | if Rails.env.test? || Rails.env.development? 6 | require "standard/rake" 7 | 8 | Rake::Task[:default].clear 9 | task default: %i[standard spec] 10 | end 11 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | = render 'shared/header' 4 | = javascript_include_tag 'application' 5 | %body 6 | #holder 7 | = render 'shared/nav' 8 | .container.content 9 | = yield 10 | = render 'shared/footer' 11 | -------------------------------------------------------------------------------- /spec/features/docs_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Docs" do 4 | context "root" do 5 | before do 6 | visit "/docs" 7 | end 8 | it "displays docs" do 9 | expect(page.body).to include "Documentation" 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/assets/stylesheets/welcome.css: -------------------------------------------------------------------------------- 1 | .jumbotron .p { 2 | margin: 25px 0px 0px 0px; 3 | } 4 | 5 | .jumbotron a { 6 | margin-top: 10px; 7 | } 8 | 9 | .jumbotron span { 10 | margin-top: 10px; 11 | cursor: default; 12 | } 13 | 14 | .p.ideas { 15 | margin-top: 10px; 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/run-conventional-commits-check.yml: -------------------------------------------------------------------------------- 1 | name: ☢️ Conventional Commits Check 2 | 3 | on: 4 | pull_request: 5 | types: [opened, edited] 6 | 7 | jobs: 8 | run-conventional-commits-check: 9 | uses: artsy/duchamp/.github/workflows/conventional-commits-check.yml@main 10 | -------------------------------------------------------------------------------- /app/helpers/cache_helper.rb: -------------------------------------------------------------------------------- 1 | module CacheHelper 2 | def no_cache! 3 | response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" 4 | response.headers["Pragma"] = "no-cache" 5 | response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/views/playground/index.html.haml: -------------------------------------------------------------------------------- 1 | .swagger{ "data-swagger-options" => @options && @options.to_json } 2 | 3 | .container 4 | .jumbotron 5 | %h1 Playground 6 | Try out some APIs. 7 | 8 | .swagger-section 9 | #swagger-ui 10 | 11 | = javascript_include_tag 'playground' 12 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path("..", __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | exec "yarnpkg", *ARGV 5 | rescue Errno::ENOENT 6 | warn "Yarn executable was not detected in the system." 7 | warn "Download Yarn at https://yarnpkg.com/en/docs/install" 8 | exit 1 9 | end 10 | -------------------------------------------------------------------------------- /app/assets/stylesheets/webhook_deliveries.css: -------------------------------------------------------------------------------- 1 | .response-status-success { 2 | background-color: #d4edda; 3 | } 4 | .response-status-warning { 5 | background-color: #fff3cd; 6 | } 7 | .response-status-danger { 8 | background-color: #f8d7da; 9 | } 10 | .response-status-default { 11 | background-color: #f8f9fa; 12 | } 13 | -------------------------------------------------------------------------------- /app/models/client_application.rb: -------------------------------------------------------------------------------- 1 | class ClientApplication 2 | include ActiveModel::Model 3 | 4 | attr_accessor :id, :name, :client_id, :client_secret, :enabled, :published_artworks_access_enabled, :redirect_urls, :created_at, :updated_at, :api_version, :roles 5 | 6 | def persisted? 7 | !id.nil? 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/content/v1/index.md: -------------------------------------------------------------------------------- 1 | ## Partner API Documentation 2 | 3 | ### General 4 | 5 | * [Getting Started](/v1/start) 6 | * [Terms of Use](/v1/terms) 7 | * [Code Samples](https://github.com/artsy/artsy-api-v1-samples) 8 | 9 | ### Tools 10 | 11 | * [API Playground](/v1/playground) 12 | * [Swagger JSON](#{ArtsyApi::V1.docs_url}) 13 | -------------------------------------------------------------------------------- /lib/templates/haml/scaffold/_form.html.haml: -------------------------------------------------------------------------------- 1 | = simple_form_for(@<%= singular_table_name %>) do |f| 2 | = f.error_notification 3 | 4 | .form-inputs 5 | <%- attributes.each do |attribute| -%> 6 | = f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> 7 | <%- end -%> 8 | 9 | .form-actions 10 | = f.button :submit 11 | -------------------------------------------------------------------------------- /app/services/client_application_partner_service.rb: -------------------------------------------------------------------------------- 1 | class ClientApplicationPartnerService 2 | def self.fetch_partners(client_application_id, access_token) 3 | url = "#{Gravity::GRAVITY_V1_API_URL}/client_application/#{client_application_id}/client_application_partners" 4 | Gravity.get(url: url, additional_headers: {"X-Access-Token" => access_token}) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/docs.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > General > Machine Description 2 | 3 | ## Machine Description 4 | 5 | An automatically generated and machine-readable [swagger](https://github.com/wordnik/swagger-spec) description of the API is available [here](#{ArtsyApi::V2.docs_url}). The [playground](/v2/playground) is notably generated using it. 6 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | 3 | require "spec_helper" 4 | require File.expand_path("../config/environment", __dir__) 5 | 6 | Dir[Rails.root.join("spec/support/**/*.rb")].sort.each { |f| require f } 7 | 8 | RSpec.configure do |config| 9 | config.use_transactional_fixtures = true 10 | config.infer_spec_type_from_file_location! 11 | end 12 | -------------------------------------------------------------------------------- /spec/controllers/health_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe HealthController, type: :controller do 4 | describe "GET #ping" do 5 | it "returns a successful response with status ok" do 6 | get :ping 7 | expect(response.status).to eq(200) 8 | expect(response.parsed_body).to eq({"status" => "ok"}) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/welcome_controller.rb: -------------------------------------------------------------------------------- 1 | class WelcomeController < ApplicationController 2 | skip_before_action :require_artsy_authentication 3 | 4 | def index 5 | @artworks_count = Rails.cache.fetch "artworks_count/#{ENV["ARTSY_API_CLIENT_ID"]}", expires_in: 12.hours do 6 | ArtsyApi::V2.artworks_count 7 | end 8 | rescue 9 | # ignore 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/help/show.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Help 2 | 3 | .p 4 | For reporting security issues, please see instructions at 5 | = succeed '.' do 6 | =link_to 'artsy.net/security', 'https://artsy.net/security', target: '_blank' 7 | 8 | .p 9 | For any other inquiries, please e-mail 10 | = succeed '.' do 11 | =link_to 'support@artsy.net', 'mailto:support@artsy.net', target: '_blank' 12 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User 2 | include ActiveModel::Model 3 | include ActiveModel::Serialization 4 | 5 | attr_accessor :id, :name, :email, :access_token 6 | 7 | def attributes 8 | { 9 | id: id, 10 | name: name, 11 | email: email, 12 | access_token: access_token 13 | } 14 | end 15 | 16 | def persisted? 17 | !id.nil? 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/controllers/concerns/paginatable.rb: -------------------------------------------------------------------------------- 1 | module Paginatable 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | before_action :set_pagination 6 | end 7 | 8 | def set_pagination 9 | @page = params[:page] || 1 10 | @size = params[:size] || 10 11 | end 12 | 13 | def calculate_total_pages(total_records, size) 14 | (total_records / size.to_f).ceil 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | -------------------------------------------------------------------------------- /config/gravity.yml: -------------------------------------------------------------------------------- 1 | development: &default 2 | api_root: <%= "#{ENV['ARTSY_API_URL'] || 'https://stagingapi.artsy.net'}" %> 3 | api_v1_root: <%= "#{ENV['ARTSY_API_URL'] || 'https://stagingapi.artsy.net'}/api/v1" %> 4 | test: 5 | <<: *default 6 | api_root: "http://doppler-test.test.net" 7 | api_v1_root: "http://doppler-test.test.net/api/v1" 8 | staging: 9 | <<: *default 10 | production: 11 | <<: *default 12 | -------------------------------------------------------------------------------- /app/models/webhook_delivery.rb: -------------------------------------------------------------------------------- 1 | class WebhookDelivery 2 | include ActiveModel::Model 3 | 4 | attr_accessor :id, :response_status, :error_class, :created_at, :completed_at, :webhook_event, :webhook_id, :webhook_url 5 | 6 | def initialize(attributes = {}) 7 | super 8 | if attributes[:webhook_event].present? 9 | self.webhook_event = WebhookEvent.new(attributes[:webhook_event]) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def status_class(response_status) 3 | case response_status 4 | when 200 5 | "response-status-success" # Green background for success 6 | when 400..499 7 | "response-status-warning" # Yellow background for client errors 8 | when 500..599 9 | "response-status-danger" # Red background for server errors 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /scripts/ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set +x 4 | wget https://github.com/mozilla/geckodriver/releases/download/v0.33.0/geckodriver-v0.33.0-linux64.tar.gz -O /tmp/geckodriver.tar.gz && \ 5 | tar xvf /tmp/geckodriver.tar.gz -C /opt && \ 6 | ln -fs /opt/geckodriver /usr/bin/geckodriver 7 | 8 | apt-get update && \ 9 | apt-get install -y xvfb firefox-esr 10 | 11 | firefox --version 12 | geckodriver --version 13 | 14 | xvfb-run bundle exec rake 15 | -------------------------------------------------------------------------------- /app/assets/javascripts/msie.js: -------------------------------------------------------------------------------- 1 | // see http://stackoverflow.com/questions/14923301/uncaught-typeerror-cannot-read-property-msie-of-undefined-jquery-tools 2 | 3 | jQuery.browser = {}; 4 | 5 | (function () { 6 | jQuery.browser.msie = false; 7 | jQuery.browser.version = 0; 8 | if (navigator.userAgent.match(/MSIE ([0-9]+)\./)) { 9 | jQuery.browser.msie = true; 10 | jQuery.browser.version = RegExp.$1; 11 | } 12 | })(); 13 | -------------------------------------------------------------------------------- /spec/features/v2/playground_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Playground" do 4 | before do 5 | allow(ArtsyApi::V2).to receive(:artworks_count).and_return(123) 6 | end 7 | it "requires authentication" do 8 | expect_any_instance_of(ApplicationController).to receive(:require_artsy_authentication) 9 | visit "/v2/playground" 10 | end 11 | context "logged in" do 12 | pending "displays swagger ui" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/features/welcome_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Welcome" do 4 | before do 5 | allow(ArtsyApi::V2).to receive(:artworks_count).and_return(123) 6 | end 7 | context "page" do 8 | before do 9 | visit "/" 10 | end 11 | it "renders a welcome message" do 12 | expect(page.body).to include "The Art World in Your App" 13 | end 14 | end 15 | pending "signs in" 16 | pending "signs out" 17 | end 18 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /app/helpers/artsy_api/v1.rb: -------------------------------------------------------------------------------- 1 | # TODO: Future cleanup, this class only fetches the docs url and data for the API playground 2 | # Slightly different from a library client 3 | 4 | module ArtsyApi 5 | module V1 6 | def self.url 7 | ENV["ARTSY_API_URL"] || "http://localhost:3000" 8 | end 9 | 10 | def self.root 11 | [url, "api/v1"].join("/") 12 | end 13 | 14 | def self.docs_url 15 | [root, "swagger_doc"].join("/") 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /config/initializers/hyperclient.rb: -------------------------------------------------------------------------------- 1 | require "hyperclient/entry_point" 2 | 3 | module Hyperclient 4 | class EntryPoint < Link 5 | def default_faraday_block 6 | lambda do |faraday| 7 | faraday.use FaradayMiddleware::FollowRedirects 8 | faraday.use Faraday::Response::RaiseError 9 | faraday.request :json 10 | faraday.response :json, content_type: /\bjson$/ 11 | faraday.adapter :net_http 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/rate_limiting.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > General > Rate Limiting 2 | 3 | ## Rate Limiting 4 | 5 | Artsy API is currently rate-limited to 5 requests per second per client application ID. API calls will return an HTTP 429 `Too many requests` error when your client is over that limit. You will not be blacklisted by default, only throttled. 6 | 7 | We currently do not offer a way to raise this limit, please [contact us](https://developers.artsy.net/help) if you need help. 8 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 4 | # Use this to limit dissemination of sensitive information. 5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | -------------------------------------------------------------------------------- /config/initializers/datadog.rb: -------------------------------------------------------------------------------- 1 | Datadog.configure do |c| 2 | c.tracing.enabled = ENV["DATADOG_ENABLED"].present? 3 | c.agent.host = ENV["TRACE_AGENT_HOSTNAME"] || "datadog" 4 | c.diagnostics.debug = false 5 | 6 | c.tracing.instrument :rails, service_name: "doppler", controller_service: "doppler.controller", cache_service: "doppler.cache", database_service: "doppler.postgres" 7 | c.tracing.instrument :redis, service_name: "doppler.redis" 8 | c.tracing.instrument :http, service_name: "doppler.http", distributed_tracing: true 9 | end 10 | -------------------------------------------------------------------------------- /spec/requests/v2/playground_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Public API Playground" do 4 | before do 5 | allow_any_instance_of(ApplicationController).to receive(:require_artsy_authentication) 6 | allow(ArtsyApi::V2).to receive(:artworks_count).and_return(123) 7 | end 8 | context "logged in" do 9 | before do 10 | get "/v2/playground" 11 | end 12 | it "sets nocache headers" do 13 | expect(response.status).to eq 200 14 | expect(response.headers["Pragma"]).to eq "no-cache" 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /DEV.md: -------------------------------------------------------------------------------- 1 | ## Dev Stuff 2 | 3 | ### Upgrading Swagger-UI 4 | 5 | Get the latest version of [swagger-ui](https://github.com/swagger-api/swagger-ui) from the [swagger-ui-dist distribution channel](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/installation.md). 6 | 7 | ``` 8 | npm install swagger-ui-dist 9 | ``` 10 | 11 | Replace everything in [vendor/assets/swagger-ui-dist](vendor/assets/swagger-ui-dist) with the files from that distribution. 12 | 13 | Make sure it works locally against staging, see [CONTRIBUTING](CONTRIBUTING.md) for how to setup Doppler locally. 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | # Limit to 0 to enable only security updates: 8 | open-pull-requests-limit: 0 9 | assignees: 10 | - mc-jones 11 | reviewers: 12 | - artsy/velocity-engineers 13 | - package-ecosystem: bundler 14 | directory: "/" 15 | schedule: 16 | interval: daily 17 | # Limit to 0 to enable only security updates: 18 | open-pull-requests-limit: 0 19 | assignees: 20 | - mc-jones 21 | reviewers: 22 | - artsy/velocity-engineers 23 | -------------------------------------------------------------------------------- /vendor/assets/swagger-ui-dist/absolute-path.js: -------------------------------------------------------------------------------- 1 | /* 2 | * getAbsoluteFSPath 3 | * @return {string} When run in NodeJS env, returns the absolute path to the current directory 4 | * When run outside of NodeJS, will return an error message 5 | */ 6 | const getAbsoluteFSPath = function () { 7 | // detect whether we are running in a browser or nodejs 8 | if (typeof module !== "undefined" && module.exports) { 9 | return require("path").resolve(__dirname) 10 | } 11 | throw new Error('getAbsoluteFSPath can only be called within a Nodejs environment'); 12 | } 13 | 14 | module.exports = getAbsoluteFSPath 15 | -------------------------------------------------------------------------------- /config/puma.config: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env puma 2 | 3 | directory "/app" 4 | environment ENV.fetch("RAILS_ENV") { "development" } 5 | pidfile "/shared/pids/puma.pid" 6 | state_path "/shared/pids/puma.state" 7 | threads ENV.fetch("PUMA_THREAD_MIN") { 0 }.to_i, ENV.fetch("PUMA_THREAD_MAX") { 16 }.to_i 8 | bind ENV.fetch('PUMA_BIND') { 'tcp://0.0.0.0:8080' } 9 | activate_control_app "unix:///shared/sockets/pumactl.sock", { no_token: true } 10 | workers ENV.fetch("PUMA_WORKERS") { 1 }.to_i 11 | worker_timeout ENV.fetch("PUMA_WORKER_TIMEOUT") { 60 }.to_i 12 | worker_boot_timeout ENV.fetch("PUMA_WORKER_BOOT_TIMEOUT") { 60 }.to_i 13 | prune_bundler 14 | -------------------------------------------------------------------------------- /app/controllers/playground_controller.rb: -------------------------------------------------------------------------------- 1 | class PlaygroundController < ApplicationController 2 | before_action :no_cache! 3 | 4 | def index 5 | @options = case request.env["PATH_INFO"] 6 | when "/v1/playground" then options_v1 7 | when "/v2/playground" then options_v2 8 | end 9 | end 10 | 11 | private 12 | 13 | def options_v2 14 | { 15 | access_token: session[:access_token], 16 | api_docs_url: ArtsyApi::V2.docs_url 17 | } 18 | end 19 | 20 | def options_v1 21 | { 22 | access_token: session[:access_token], 23 | api_docs_url: ArtsyApi::V1.docs_url 24 | } 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/assets/javascripts/playground.js.coffee: -------------------------------------------------------------------------------- 1 | //= require swagger-ui-dist 2 | 3 | $ -> 4 | 5 | options = $(".swagger").data("swagger-options") 6 | 7 | ui = SwaggerUIBundle({ 8 | url: options.api_docs_url, 9 | dom_id: "#swagger-ui", 10 | deepLinking: true, 11 | presets: [ 12 | SwaggerUIBundle.presets.apis, 13 | SwaggerUIStandalonePreset 14 | ], 15 | plugins: [ 16 | SwaggerUIBundle.plugins.DownloadUrl 17 | ], 18 | layout: "StandaloneLayout", 19 | requestInterceptor: (req) => 20 | req.headers["X-Access-Token"] = options.access_token 21 | return req 22 | }) 23 | 24 | window.ui = ui 25 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | README.md 4 | 5 | # 6 | # OS X 7 | # 8 | .DS_Store 9 | .AppleDouble 10 | .LSOverride 11 | 12 | # 13 | # Rails 14 | # 15 | .env 16 | .env.sample 17 | *.rbc 18 | capybara-*.html 19 | .rspec 20 | log 21 | tmp 22 | db/*.sqlite3 23 | db/*.sqlite3-journal 24 | public/system 25 | coverage/ 26 | spec/tmp 27 | **.orig 28 | rerun.txt 29 | 30 | # TODO Comment out these rules if you are OK with secrets being uploaded to the repo 31 | #config/initializers/secret_token.rb 32 | #config/secrets.yml 33 | 34 | ## Environment normalisation: 35 | .bundle 36 | 37 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 38 | .rvmrc -------------------------------------------------------------------------------- /app/views/shared/_footer.html.haml: -------------------------------------------------------------------------------- 1 | %footer 2 | © 3 | =Date.today.year 4 | =link_to 'Artsy', 'https://www.artsy.net', target: '_blank' 5 | | 6 | =link_to 'About', 'https://www.artsy.net/about', target: '_blank' 7 | | 8 | =link_to 'Open Source', 'https://artsy.github.io/open-source/', target: '_blank' 9 | | 10 | =link_to 'Privacy', 'https://artsy.net/privacy', target: '_blank' 11 | | 12 | =link_to 'Security', 'https://artsy.net/security', target: '_blank' 13 | | 14 | =link_to image_tag('blacktocat-16.png', border: 0), 'https://github.com/artsy/doppler', target: '_blank' 15 | =link_to image_tag('bird_blue_16.png', boder: 0), 'https://twitter.com/artsyopensource' 16 | -------------------------------------------------------------------------------- /config/locales/en.bootstrap.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | breadcrumbs: 6 | application: 7 | root: "Index" 8 | pages: 9 | pages: "Pages" 10 | helpers: 11 | actions: "Actions" 12 | links: 13 | back: "Back" 14 | cancel: "Cancel" 15 | confirm: "Are you sure?" 16 | destroy: "Delete" 17 | new: "New" 18 | edit: "Edit" 19 | titles: 20 | edit: "Edit %{model}" 21 | save: "Save %{model}" 22 | new: "New %{model}" 23 | delete: "Delete %{model}" 24 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /spec/features/v2/index_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Docs" do 4 | context "root" do 5 | before do 6 | visit "/v2" 7 | end 8 | it "displays docs" do 9 | expect(page.body).to include "Public API Documentation" 10 | end 11 | end 12 | Rails.application.routes.routes 13 | .map { |r| r.path.source.match(%r{/([/\w]*)})[0] } 14 | .select { |path| path.starts_with?("/v2/docs/") }.each do |path| 15 | context path do 16 | before do 17 | visit path 18 | sleep 1 19 | end 20 | it "displays a breadcrumb back to docs" do 21 | expect(page).to have_css "a[href='/v2']" 22 | end 23 | it "does not include any italics" do 24 | expect(page).to_not have_css "em" 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/features/v1/index_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Partner API" do 4 | context "root" do 5 | before do 6 | visit "/v1" 7 | end 8 | it "displays docs" do 9 | expect(page.body).to include "Partner API Documentation" 10 | end 11 | end 12 | Rails.application.routes.routes 13 | .map { |r| r.path.source.match(%r{/([/\w]*)})[0] } 14 | .select { |path| path.starts_with?("/v1/docs/") }.each do |path| 15 | context path do 16 | before do 17 | visit path 18 | sleep 1 19 | end 20 | it "displays a breadcrumb back to docs" do 21 | expect(page).to have_css "a[href='/v1']" 22 | end 23 | it "does not include any italics" do 24 | expect(page).to_not have_css "em" 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | /* 2 | *=require bootstrap_and_overrides 3 | */ 4 | 5 | .navbar-brand { 6 | background-image: image-url("logo.png"); 7 | background-size: 40px; 8 | background-repeat: no-repeat; 9 | background-position: left center; 10 | padding-left: 70px; 11 | } 12 | 13 | pre { 14 | margin: 10px 0px; 15 | } 16 | 17 | h4 { 18 | padding-top: 20px; 19 | } 20 | 21 | html, body { 22 | height: 100%; 23 | } 24 | 25 | #holder { 26 | min-height: 100%; 27 | position:relative; 28 | } 29 | 30 | #body { 31 | padding-bottom: 100px; 32 | } 33 | 34 | #holder footer { 35 | height: 30px; 36 | width: 100%; 37 | position: absolute; 38 | left: 0; 39 | bottom: 0; 40 | text-align: center; 41 | font-size: 12px; 42 | } 43 | 44 | #holder .container.content { 45 | padding-bottom: 50px; 46 | } 47 | -------------------------------------------------------------------------------- /config/locales/simple_form.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | simple_form: 3 | "yes": 'Yes' 4 | "no": 'No' 5 | required: 6 | text: 'required' 7 | mark: '*' 8 | # You can uncomment the line below if you need to overwrite the whole required html. 9 | # When using html, text and mark won't be used. 10 | # html: '*' 11 | error_notification: 12 | default_message: "Please review the problems below:" 13 | # Labels and hints examples 14 | # labels: 15 | # defaults: 16 | # password: 'Password' 17 | # user: 18 | # new: 19 | # email: 'E-mail to sign in.' 20 | # edit: 21 | # email: 'E-mail.' 22 | # hints: 23 | # defaults: 24 | # username: 'User name to sign in.' 25 | # password: 'No special characters, please.' 26 | 27 | -------------------------------------------------------------------------------- /app/services/client_application_service.rb: -------------------------------------------------------------------------------- 1 | class ClientApplicationService 2 | def self.fetch_webhook_deliveries(access_token, params = {}) 3 | url = "#{Gravity::GRAVITY_V1_API_URL}/webhook_deliveries" 4 | Gravity.get(url: url, additional_headers: {"X-Access-Token" => access_token}, params: params) 5 | end 6 | 7 | def self.fetch_webhook_delivery(access_token, params = {}) 8 | url = "#{Gravity::GRAVITY_V1_API_URL}/webhook_delivery/#{params[:id]}" 9 | Gravity.get(url: url, additional_headers: {"X-Access-Token" => access_token}, params: params) 10 | end 11 | 12 | def self.redeliver_webhook_delivery(access_token, params = {}) 13 | url = "#{Gravity::GRAVITY_V1_API_URL}/webhook_delivery/#{params[:id]}/redeliver" 14 | Gravity.post(url: url, additional_headers: {"X-Access-Token" => access_token}, params: params) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | include CacheHelper 3 | include ArtsyAuth::Authenticated 4 | 5 | protect_from_forgery with: :exception 6 | before_action :set_sentry_context 7 | helper_method :authenticated? 8 | 9 | def artsy_client 10 | @client ||= if authenticated? 11 | ArtsyApi::V2.client(access_token: session[:access_token]) 12 | else 13 | ArtsyApi::V2.client(xapp_token: ArtsyApi::V2.xapp_token) 14 | end 15 | end 16 | 17 | def set_sentry_context 18 | return unless authenticated? 19 | 20 | Sentry.set_user(user_id: session[:user_id], email: session[:email]) 21 | end 22 | 23 | def authenticated? 24 | session[:user_id].present? 25 | end 26 | 27 | def authorized_artsy_token?(token) 28 | JWT.decode(token, ENV["JWT_SECRET"]) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/views/welcome/index.html.haml: -------------------------------------------------------------------------------- 1 | .jumbotron 2 | %h1 3 | The Art World in Your App 4 | %span.vertical-align-top.tiny ™ 5 | .p 6 | The Artsy Public API provides access to images of historic artwork and related information on artsy.net for educational and other non-commercial purposes. 7 | In comparison to the set of data on artsy.net this API has limited scope. For example, only public domain works are currently accessible. 8 | By using this API, you agree to our API Terms of Use. 9 | .p 10 | The Artsy Partner API enables select partners to upload artworks, manage partner artists and more. 11 | Please contact us for access to this API and all other inquiries. 12 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path("..", __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system! "bin/rails log:clear tmp:clear" 25 | 26 | puts "\n== Restarting application server ==" 27 | system! "bin/rails restart" 28 | end 29 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.precompile = ["*.js", "*.css", "*.png", "*.eot", "*.woff", "*.ttf"] 10 | Rails.application.config.assets.paths << Rails.root.join("node_modules") 11 | Rails.application.config.assets.paths << Rails.root.join("vendor/assets") 12 | 13 | # Precompile additional assets. 14 | # application.js, application.css, and all non-JS/CSS in the app/assets 15 | # folder are already added. 16 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 17 | -------------------------------------------------------------------------------- /spec/features/v2/authentication_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Docs/authentication" do 4 | before do 5 | visit "/v2/docs/authentication" 6 | end 7 | it "displays authentication" do 8 | expect(page.body).to include "Authenticate with an XAPP Token" 9 | end 10 | it "syntax highlights code" do 11 | expect(page.all(".CodeRay .code pre").count).to be > 0 12 | end 13 | it "adds style to tables" do 14 | expect(page.all("table.table-bordered.table-striped").count).to be > 0 15 | end 16 | it "marks up alert blocks" do 17 | expect(page.all("div.alert.alert-warning").count).to be > 0 18 | expect(page).to have_css "div.alert.alert-warning", text: "This will be implemented in the future." 19 | end 20 | it "includes correct oauth2 links" do 21 | expect(page.body).to include 'curl -v "http://localhost:3000/oauth2/authorize' 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /vendor/assets/swagger-ui-dist/index.js: -------------------------------------------------------------------------------- 1 | try { 2 | module.exports.SwaggerUIBundle = require("./swagger-ui-bundle.js") 3 | module.exports.SwaggerUIStandalonePreset = require("./swagger-ui-standalone-preset.js") 4 | } catch(e) { 5 | // swallow the error if there's a problem loading the assets. 6 | // allows this module to support providing the assets for browserish contexts, 7 | // without exploding in a Node context. 8 | // 9 | // see https://github.com/swagger-api/swagger-ui/issues/3291#issuecomment-311195388 10 | // for more information. 11 | } 12 | 13 | // `absolutePath` and `getAbsoluteFSPath` are both here because at one point, 14 | // we documented having one and actually implemented the other. 15 | // They were both retained so we don't break anyone's code. 16 | module.exports.absolutePath = require("./absolute-path.js") 17 | module.exports.getAbsoluteFSPath = require("./absolute-path.js") 18 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.3.0 2 | 3 | ENV PORT 8080 4 | EXPOSE 8080 5 | WORKDIR /app 6 | 7 | # nodejs: for Rails assets 8 | # tzdata: fix TZInfo::DataSourceNotFound on start 9 | # linux-headers: for raindrops that is required by Unicorn 10 | # bash: for debugging in production 11 | 12 | RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - \ 13 | && apt-get update -qq \ 14 | && apt-get install -y build-essential nodejs tzdata dumb-init \ 15 | && rm -rf /var/lib/apt/lists/* 16 | 17 | COPY Gemfile* ./ 18 | 19 | RUN gem install bundler && bundle update --bundler \ 20 | && bundle 21 | 22 | COPY . ./ 23 | 24 | # Setup Rails shared folders for Puma / Nginx 25 | RUN mkdir /shared 26 | RUN mkdir /shared/config 27 | RUN mkdir /shared/pids 28 | RUN mkdir /shared/sockets 29 | RUN bundle exec rake assets:precompile 30 | 31 | ENTRYPOINT ["/usr/bin/dumb-init", "./scripts/load_secrets_and_run.sh"] 32 | 33 | CMD ["bundle", "exec", "puma", "-C", "config/puma.config"] 34 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/status.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Status 2 | 3 | ``` alert[info] 4 | This API endpoint does not require authentication or authorization. 5 | ``` 6 | 7 | ## Status API 8 | 9 | This API retrieves the system status. 10 | 11 | ``` 12 | curl -v "#{ArtsyApi::V2.root}/status" 13 | ``` 14 | 15 | This endpoint does not accept any parameters and returns a status JSON document with the following fields. 16 | 17 | #{modelref://System} 18 | 19 | #### Possible Status Values 20 | 21 | Status | Condition | 22 | -------------:|:---------------------------------------------------| 23 | good | All systems operational. | 24 | minor | Some parts of the API may not be fully functioning.| 25 | major | System is down and is experiencing a major outage. | 26 | 27 | #### Example 28 | 29 | ``` json 30 | #{resource://status} 31 | ``` 32 | -------------------------------------------------------------------------------- /vendor/assets/swagger-ui-dist/README.md: -------------------------------------------------------------------------------- 1 | # Swagger UI Dist 2 | [![NPM version](https://badge.fury.io/js/swagger-ui-dist.svg)](http://badge.fury.io/js/swagger-ui-dist) 3 | 4 | # API 5 | 6 | This module, `swagger-ui-dist`, exposes Swagger-UI's entire dist folder as a dependency-free npm module. 7 | Use `swagger-ui` instead, if you'd like to have npm install dependencies for you. 8 | 9 | `SwaggerUIBundle` and `SwaggerUIStandalonePreset` can be imported: 10 | ```javascript 11 | import { SwaggerUIBundle, SwaggerUIStandalonePreset } from "swagger-ui-dist" 12 | ``` 13 | 14 | To get an absolute path to this directory for static file serving, use the exported `getAbsoluteFSPath` method: 15 | 16 | ```javascript 17 | const swaggerUiAssetPath = require("swagger-ui-dist").getAbsoluteFSPath() 18 | 19 | // then instantiate server that serves files from the swaggerUiAssetPath 20 | ``` 21 | 22 | For anything else, check the [Swagger-UI](https://github.com/swagger-api/swagger-ui) repository. 23 | -------------------------------------------------------------------------------- /spec/requests/docs/resources/applications_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | describe "Resources/applications" do 3 | before do 4 | allow(ArtsyApi::V2).to receive_message_chain(:client, :links, :applications, :embedded, :applications).and_return([]) 5 | models_json = { 6 | "models" => { 7 | "Application" => { 8 | "properties" => { 9 | "client_id" => {"description" => "Application client id."} 10 | } 11 | } 12 | } 13 | } 14 | allow(ArtsyApi::V2).to receive_message_chain(:client, :connection, :get, :body).and_return(models_json) 15 | end 16 | context "signed in" do 17 | before do 18 | allow_any_instance_of(ApplicationController).to receive(:authenticated?).and_return(true) 19 | get "/v2/docs/applications" 20 | end 21 | it "sets nocache headers" do 22 | expect(response.status).to eq 200 23 | expect(response.headers["Cache-Control"]).to include("private") 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/controllers/v1/start_controller.rb: -------------------------------------------------------------------------------- 1 | module V1 2 | class StartController < ApplicationController 3 | skip_before_action :require_artsy_authentication 4 | 5 | def show 6 | return unless authenticated? 7 | 8 | @client_applications = artsy_client.try(:applications) || [] 9 | 10 | # the UI needs to ask the user to create an app, then an admin needs to enable it for v1 11 | # so we only display v1 apps in case there's at least one 12 | @client_applications_v1 = @client_applications.to_a.select { |ca| ca.api_version == 1 } 13 | @client_applications = @client_applications_v1 if @client_applications_v1.any? 14 | 15 | @selected_client_application = @client_applications.find { |app| app.id == params[:id] } if params.key?(:id) 16 | @selected_client_application ||= @client_applications.first if @client_applications.count == 1 17 | no_cache! if @selected_client_application 18 | rescue => e 19 | @error = e 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 8c2c07414ea18022237844f38821e434754297b991e75275f28c5574cfeeec34dc7bba07c7d0db7eaaef61675a90256936e5704eb3744b02d0f7e4e26df709a5 15 | 16 | test: 17 | secret_key_base: fe30775228309a1412b218a39a8c1eb9096f3b7ce2a4025478fe4168d7ebe8c02af7016be9e44dfc45fc55c31c23eaa95b97b33093d480381a9cabbdbce5ef37 18 | 19 | staging: 20 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 21 | 22 | production: 23 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 24 | -------------------------------------------------------------------------------- /app/helpers/open_redirect_helper.rb: -------------------------------------------------------------------------------- 1 | module OpenRedirectHelper 2 | def safe_redirect_uri(redirect_uri) 3 | return nil if redirect_uri.blank? 4 | 5 | begin 6 | # Build the root URI using request details 7 | root_url = URI.parse("#{request.protocol}#{request.host_with_port}") 8 | parsed_url = URI.join(root_url.to_s, redirect_uri.to_s) 9 | 10 | # Check if parsed URL is a data URI or another unsafe scheme 11 | return nil if parsed_url.scheme == "data" 12 | 13 | # Ensure URL uses HTTP or HTTPS and matches the host and scheme of the request 14 | return nil unless parsed_url.is_a?(URI::HTTP) || parsed_url.is_a?(URI::HTTPS) 15 | return parsed_url.to_s if parsed_url.host.nil? && parsed_url.relative? 16 | return parsed_url.to_s if parsed_url.scheme == request.scheme && parsed_url.host == request.host && parsed_url.port == request.port 17 | 18 | # Return nil for all other cases 19 | nil 20 | rescue URI::InvalidURIError 21 | nil 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/features/v2/resources/applications_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Resources/applications" do 4 | before do 5 | allow(ArtsyApi::V2).to receive_message_chain(:client, :links, :applications, :embedded, :applications).and_return([]) 6 | models_json = { 7 | "models" => { 8 | "Application" => { 9 | "properties" => { 10 | "client_id" => {"description" => "Application client id."} 11 | } 12 | } 13 | } 14 | } 15 | allow(ArtsyApi::V2).to receive_message_chain(:client, :connection, :get, :body).and_return(models_json) 16 | end 17 | context "signed out" do 18 | before do 19 | visit "/v2/docs/applications" 20 | end 21 | it "displays a generic curl path" do 22 | expect(page).to have_css "code", text: 'curl -v "http://localhost:3000/api/applications?user_id=USER_ID" -H "X-Access-Token: ACCESS_TOKEN"' 23 | end 24 | it "displays model ref" do 25 | expect(page).to have_css "td", text: "Application client id." 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ruby "3.3.0" 4 | 5 | gem "rails", "7.1.4.1" 6 | 7 | gem "artsy-auth" 8 | gem "bootstrap_form" 9 | gem "coderay" 10 | gem "coffee-rails" 11 | gem "dalli" 12 | gem "ddtrace" 13 | gem "faraday", "~> 0.9" 14 | gem "foreman" 15 | gem "google-analytics-rails" 16 | gem "haml-rails" 17 | gem "hiredis" 18 | gem "hyperclient", "~> 0.9.3" 19 | gem "jquery-rails" 20 | gem "nokogiri" 21 | gem "oauth2", "1.4.9" 22 | gem "puma" 23 | gem "redcarpet" 24 | gem "redis-rails" 25 | gem "sass-rails" 26 | gem "sprockets", "< 4.0" 27 | gem "sprockets-rails" 28 | gem "sentry-ruby" 29 | gem "sentry-rails" 30 | gem "twitter-bootstrap-rails" 31 | gem "twitter-bootstrap-rails-confirm" 32 | gem "uglifier" 33 | 34 | group :development do 35 | gem "guard" 36 | gem "guard-bundler" 37 | gem "guard-coffeescript" 38 | gem "guard-rspec" 39 | gem "guard-rubocop" 40 | gem "spring" 41 | end 42 | 43 | group :development, :test do 44 | gem "byebug" 45 | gem "capybara" 46 | gem "rspec-rails" 47 | gem "standard" 48 | gem "selenium-webdriver" 49 | end 50 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard :bundler do 2 | watch "Gemfile" 3 | end 4 | 5 | guard "coffeescript", input: "app/assets/javascripts" 6 | 7 | guard :rspec, cmd: "bundle exec rspec" do 8 | watch(%r{^spec/.+_spec\.rb$}) 9 | watch(%r{^spec/support/(.+)\.rb$}) { "spec" } 10 | watch("spec/spec_helper.rb") { "spec" } 11 | watch(%r{^app/(.*)(\.js|\.css|\.scss)$}) { "spec/features" } 12 | watch("config/routes.rb") { "spec/features" } 13 | watch(%r{^app/helpers/(.*)\.rb$}) { "spec/features" } 14 | watch(%r{^app/models/(.*)\.rb$}) { "spec/features" } 15 | watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| "spec/features/#{m[1]}_spec.rb" } 16 | watch("app/controllers/application_controller.rb") { "spec/features" } 17 | watch("app/controllers/session_controller.rb") { "spec/features" } 18 | watch(%r{^app/views/(.+)/.*\.haml$}) { |m| "spec/features/#{m[1]}_spec.rb" } 19 | watch(%r{^app/views/(layouts|shared)/.*\.haml$}) { "spec/features" } 20 | end 21 | 22 | guard :rubocop do 23 | watch(/.+\.rb$/) 24 | watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) } 25 | end 26 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/editions.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Edition Sets 2 | 3 | ## Editions API 4 | 5 | An edition set is usually a collection of prints created from the same matrix. 6 | 7 | #### Retrieving an Edition 8 | 9 | Editions are embedded within an artwork, but can also be retrieved individually by ID by rendering the "edition" link template from [root](#{ArtsyApi::V2.root}). 10 | 11 | ``` 12 | curl -v "#{ArtsyApi::V2.root}/edition/{id}?artwork_id={artwork_id}" -H "X-XAPP-Token: XAPP_TOKEN" 13 | ``` 14 | 15 | ## Edition JSON Format 16 | 17 | #{modelref://Edition} 18 | 19 | #### Links 20 | 21 | Key | Target | 22 | ----------:|:--------------------------------------------------------| 23 | self | The artwork resource. | 24 | artwork | [Artwork](/v2/docs/artworks) corresponding to the edition. | 25 | 26 | #### Example 27 | 28 | ``` json 29 | #{resource://edition/id=5097e5ebac8b8d0002000a07/artwork_id=5035a0faf852da0002000781} 30 | ``` 31 | -------------------------------------------------------------------------------- /app/helpers/markdown_helper.rb: -------------------------------------------------------------------------------- 1 | module MarkdownHelper 2 | def render_markdown(text) 3 | renderer = Redcarpet::Markdown.new(Redcarpet::Render::HTML, fenced_code_blocks: true, tables: true) 4 | doc = Nokogiri::HTML::DocumentFragment.parse(renderer.render(text)) 5 | doc.css("code[@class]").each do |code| 6 | case code[:class] 7 | when /(?\w*)\[(?\w*)\]/ 8 | code["class"] = "#{$LAST_MATCH_INFO["class"]} #{$LAST_MATCH_INFO["class"]}-#{$LAST_MATCH_INFO["type"]}" 9 | code.name = "div" 10 | code.parent.swap(code) 11 | code.inner_html = render_markdown(code.inner_html) 12 | else 13 | div = highlight_syntax(code.text.rstrip, code[:class].to_sym) 14 | code = code.replace(div) 15 | code.first.parent.swap(code.first) 16 | end 17 | end 18 | doc.search("table").each do |table| 19 | table["class"] = "table table-bordered table-striped" 20 | end 21 | doc.to_s 22 | end 23 | 24 | def highlight_syntax(text, format) 25 | CodeRay.scan(text.rstrip, format).div 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/features/v2/resources/status_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Resources/status" do 4 | before do 5 | # status example 6 | allow(ArtsyApi::V2).to receive(:xapp_token).and_return("token") 7 | models_json = { 8 | "models" => { 9 | "System" => { 10 | "properties" => { 11 | "status" => {"description" => "System status description."} 12 | } 13 | } 14 | } 15 | } 16 | allow(ArtsyApi::V2).to receive_message_chain(:client, :connection, :get, :body).and_return(models_json) 17 | allow(ArtsyApi::V2).to receive_message_chain(:client, :status, :_get, :_response, :body).and_return(foo: "bar") 18 | end 19 | context "signed out" do 20 | before do 21 | visit "/v2/docs/status" 22 | end 23 | it "renders model refs" do 24 | expect(page).to have_css "td", text: "status" 25 | expect(page).to have_css "td", text: "System status description." 26 | end 27 | it "renders resources" do 28 | expect(page).to have_content '"foo": "bar"' 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /DEPLOYING.md: -------------------------------------------------------------------------------- 1 | Deploying Doppler 2 | ================= 3 | 4 | Doppler is deployed to K8 with all the configuration checked in. 5 | 6 | See [github.com/artsy/doppler/pull/154](https://github.com/artsy/doppler/pull/154) for details of switching from Heroku to K8. 7 | 8 | #### Environment Settings 9 | 10 | Add the following environment settings. The sample values below are for a staging deployment. 11 | 12 | * ARTSY_API_URL=https://stagingapi.artsy.net 13 | * ARTSY_API_CLIENT_ID= 14 | * ARTSY_API_CLIENT_SECRET= 15 | 16 | You can use a key from a demo app created on [developers-staging.artsy.net](https://developers-staging.artsy.net). Note that staging is rebuilt nightly with production data, and your application will be deleted. If you need a persistent application, create it on [developers.artsy.net](http://developers.artsy.net) - it will be available on staging within 24 hours. 17 | 18 | ### Production 19 | 20 | Assuming `upstream` is `git@github.com:artsy/doppler.git`, run the following. 21 | 22 | ``` 23 | hokusai production pipeline promote --git-remote upstream 24 | ``` 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018 Artsy, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | orbs: 3 | hokusai: artsy/hokusai@volatile 4 | horizon: artsy/release@volatile 5 | not_staging_or_release: ¬_staging_or_release 6 | filters: 7 | branches: 8 | ignore: 9 | - staging 10 | - release 11 | only_main: &only_main 12 | context: hokusai 13 | filters: 14 | branches: 15 | only: main 16 | only_release: &only_release 17 | context: hokusai 18 | filters: 19 | branches: 20 | only: release 21 | workflows: 22 | build-deploy: 23 | jobs: 24 | - hokusai/test: 25 | <<: *not_staging_or_release 26 | - hokusai/push: 27 | name: push-staging-image 28 | <<: *only_main 29 | requires: 30 | - hokusai/test 31 | - hokusai/deploy-staging: 32 | <<: *only_main 33 | project-name: 'doppler' 34 | requires: 35 | - push-staging-image 36 | - hokusai/deploy-production: 37 | <<: *only_release 38 | requires: 39 | - horizon/block 40 | - horizon/block: 41 | <<: *only_release 42 | context: horizon 43 | project_id: 41 44 | -------------------------------------------------------------------------------- /app/views/shared/_nav.html.haml: -------------------------------------------------------------------------------- 1 | = nav_bar static: :top, brand: "The art world in your app. ™".html_safe, responsive: true do 2 | = menu_group do 3 | = menu_item "Home", root_path 4 | = menu_item "My Apps", client_applications_path 5 | = menu_item "Blog", 'http://artsy.github.io', target: '_blank' 6 | = menu_item "API Docs", docs_path 7 | = menu_item "Help", help_path 8 | = menu_group pull: :right do 9 | = menu_item "Public API Terms", v2_terms_path 10 | -if authenticated? 11 | = menu_item "Sign Out", '/sign_out' 12 | -else 13 | = menu_item "Sign In", '/auth/artsy/new' 14 | = bootstrap_flash 15 | 16 | - unless request.path.include?('/v1') 17 | .alert.alert-warning 18 | %p 19 | Please note, we are in the process of retiring the 20 | = link_to 'public api.', '/v2' 21 | The public api, as well as its documentation and playground, may be taken down at any time without additional notice. 22 | This change will not affect integration partners using our 23 | = link_to 'partner api.', '/v1' 24 | %br/ 25 | %p 26 | If you're an Artsy partner gallery, reach out to your Artsy Liaison or contact our Support team for assistance. 27 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.config.content_security_policy do |policy| 8 | # policy.default_src :self, :https 9 | # policy.font_src :self, :https, :data 10 | # policy.img_src :self, :https, :data 11 | # policy.object_src :none 12 | # policy.script_src :self, :https 13 | # policy.style_src :self, :https 14 | 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | 19 | # If you are using UJS then enable automatic nonce generation 20 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 21 | 22 | # Report CSP violations to a specified URI 23 | # For further information see the following documentation: 24 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 25 | # Rails.application.config.content_security_policy_report_only = true 26 | -------------------------------------------------------------------------------- /app/helpers/artsy_api/v2.rb: -------------------------------------------------------------------------------- 1 | module ArtsyApi 2 | module V2 3 | def self.url 4 | ENV["ARTSY_API_URL"] || "http://localhost:3000" 5 | end 6 | 7 | def self.root 8 | [url, "api"].join("/") 9 | end 10 | 11 | def self.docs_url 12 | [root, "docs"].join("/") 13 | end 14 | 15 | def self.client(options = {}) 16 | Hyperclient.new(root) do |api| 17 | api.headers["Accept"] = "application/vnd.artsy-v2+json" 18 | api.headers["Content-Type"] = "application/json" 19 | api.headers["X-Access-Token"] = options[:access_token] if options.key?(:access_token) 20 | api.headers["X-Xapp-Token"] = options[:xapp_token] if options.key?(:xapp_token) 21 | end 22 | end 23 | 24 | def self.artworks_count 25 | conn = client.connection 26 | conn.headers["X-XAPP-Token"] = xapp_token 27 | conn.get("#{root}/artworks?total_count=1").body["total_count"] 28 | end 29 | 30 | def self.xapp_token 31 | Rails.cache.fetch "xapp-token/#{ENV["ARTSY_API_CLIENT_ID"]}", expires_in: 1.hour do 32 | client.tokens.xapp_token._post(client_id: ENV["ARTSY_API_CLIENT_ID"], client_secret: ENV["ARTSY_API_CLIENT_SECRET"]).token 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/gravity.rb: -------------------------------------------------------------------------------- 1 | # API Client for Gravity V1 2 | class Gravity 3 | GRAVITY_V1_API_URL = Rails.application.config_for(:gravity)["api_v1_root"] 4 | 5 | class << self 6 | class GravityError < StandardError; end 7 | 8 | class GravityNotFoundError < GravityError; end 9 | 10 | def get(url:, additional_headers: {}, params: {}) 11 | response = Faraday.get(url, params, headers.merge(additional_headers)) 12 | 13 | process(response) 14 | end 15 | 16 | def post(url:, additional_headers: {}, params: {}) 17 | response = Faraday.post(url, params, headers.merge(additional_headers)) 18 | 19 | process(response) 20 | end 21 | 22 | def process(response) 23 | raise GravityNotFoundError if response.status == 404 24 | 25 | results = JSON.parse(response.body, symbolize_names: true) 26 | raise GravityError, "Couldn't perform request! status: #{response.status}. Message: #{results[:message]}" unless response.success? 27 | 28 | # Return both the body and only the `X-Total-Count` header part of the response 29 | {body: results, headers: {"X-Total-Count" => response.headers["X-Total-Count"]}} 30 | end 31 | 32 | private 33 | 34 | def headers 35 | {} 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/requests/client_applications_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Client Applications" do 4 | let(:client) { double("Hyperclient") } 5 | context "logged in" do 6 | before do 7 | allow_any_instance_of(ApplicationController).to receive_messages( 8 | require_artsy_authentication: nil, 9 | artsy_client: client 10 | ) 11 | allow(client).to receive_message_chain(:tokens, :xapp_token, :_post).and_return("foo") 12 | end 13 | context "with an application" do 14 | let(:application) do 15 | Hashie::Mash.new( 16 | id: "1", 17 | name: "One", 18 | client_id: "client_id", 19 | client_secret: "client_secret", 20 | created_at: Time.now.utc.to_s, 21 | updated_at: Time.now.utc.to_s, 22 | _attributes: { 23 | id: "1" 24 | } 25 | ) 26 | end 27 | before do 28 | allow(client).to receive_messages(applications: [application], application: application) 29 | end 30 | it "sets nocache headers" do 31 | get "/client_applications/123" 32 | expect(response.status).to eq 200 33 | expect(response.headers["Pragma"]).to eq "no-cache" 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/views/content/v2/index.md: -------------------------------------------------------------------------------- 1 | ## Public API Documentation 2 | 3 | ### General 4 | 5 | * [Authentication](/v2/docs/authentication) 6 | * [Requests & Responses](/v2/docs/http) 7 | * [Pagination And Sets](/v2/docs/pagination) 8 | * [Links Between Resources](/v2/docs/links) 9 | * [Errors](/v2/docs/errors) 10 | * [Rate Limiting](/v2/docs/rate_limiting) 11 | * [Machine Description](/v2/docs/docs) 12 | * [Markdown Content](/v2/docs/markdown) 13 | 14 | ### Resources 15 | 16 | * [Applications](/v2/docs/applications) 17 | * [Artists](/v2/docs/artists) 18 | * [Artworks](/v2/docs/artworks) 19 | * [Bidders](/v2/docs/bidders) 20 | * [Bidder Positions](/v2/docs/bidder_positions) 21 | * [Bids](/v2/docs/bids) 22 | * [Collection Items](/v2/docs/collection_items) 23 | * [Collections](/v2/docs/collections) 24 | * [Editions](/v2/docs/editions) 25 | * [Fairs](/v2/docs/fairs) 26 | * [Genes](/v2/docs/genes) 27 | * [Images](/v2/docs/images) 28 | * [Partners](/v2/docs/partners) 29 | * [Profiles](/v2/docs/profiles) 30 | * [Sales](/v2/docs/sales) 31 | * [Search](/v2/docs/search) 32 | * [Shows](/v2/docs/shows) 33 | * [Status](/v2/docs/status) 34 | * [User Details](/v2/docs/user_details) 35 | * [Users](/v2/docs/users) 36 | 37 | ### Misc 38 | 39 | * [API Playground](/v2/playground) 40 | * [Terms of Use](/v2/terms) 41 | -------------------------------------------------------------------------------- /app/controllers/client_application_partners_controller.rb: -------------------------------------------------------------------------------- 1 | class ClientApplicationPartnersController < ApplicationController 2 | include Paginatable 3 | def index 4 | client_application_id = params[:client_application_id] 5 | response = ClientApplicationPartnerService.fetch_partners(client_application_id, session[:access_token]) 6 | 7 | @client_application_partners = response[:body].map do |partner_data| 8 | build_client_application_partner(partner_data) 9 | end 10 | 11 | @total_pages = calculate_total_pages(response[:headers]["X-Total-Count"].to_i, @size) 12 | @current_page = @page.to_i 13 | rescue => e 14 | @error = e.message 15 | end 16 | 17 | private 18 | 19 | def build_client_application_partner(data) 20 | ClientApplicationPartner.new( 21 | id: data[:id], 22 | partner_id: data[:partner_id], 23 | partner_name: data[:partner_name], 24 | client_application_id: data[:client_application_id], 25 | client_application_name: data[:client_application_name], 26 | created_at: data[:created_at], 27 | updated_at: data[:updated_at] 28 | ) 29 | end 30 | 31 | def client_application_partner_params 32 | params.require(:client_application_partner).permit(:client_application_id, :partner_id) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /app/assets/stylesheets/bootstrap_and_overrides.css: -------------------------------------------------------------------------------- 1 | /* 2 | *=require twitter-bootstrap-static/bootstrap 3 | */ 4 | 5 | /* vertically align all TDs */ 6 | .table tbody > tr > td { 7 | vertical-align: middle; 8 | } 9 | 10 | /* sections disabled in getting started */ 11 | .disabled { 12 | color: #ccc; 13 | } 14 | 15 | /* most tables are name | value, make name fixed size */ 16 | tr td:first-child { 17 | width: 200px; 18 | } 19 | 20 | .container .jumbotron { 21 | border-radius: 0; 22 | background-color: white; 23 | border: 2px solid black; 24 | } 25 | 26 | .navbar-default { 27 | background-color: white; 28 | } 29 | 30 | .navbar-default .navbar-nav li a { 31 | color: black; 32 | } 33 | 34 | .navbar-default .navbar-nav li.active a { 35 | background-color: white; 36 | text-decoration: underline; 37 | font-weight: bold; 38 | color: black; 39 | } 40 | 41 | .navbar-default .navbar-nav li a:hover { 42 | text-decoration: underline; 43 | } 44 | 45 | .navbar-default .navbar-nav li.active a:hover { 46 | background-color: white; 47 | } 48 | 49 | .navbar-default .navbar-brand { 50 | color: black; 51 | font-style: italic; 52 | font-family: 'Times New Roman', Times, serif; 53 | } 54 | 55 | .tiny { 56 | font-size: 33%; 57 | } 58 | 59 | .vertical-align-top { 60 | vertical-align: top 61 | } 62 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | require "active_job/railtie" 7 | # require "active_record/railtie" 8 | # require "active_storage/engine" 9 | require "action_controller/railtie" 10 | require "action_mailer/railtie" 11 | # require "action_mailbox/engine" 12 | # require "action_text/engine" 13 | require "action_view/railtie" 14 | # require "action_cable/engine" 15 | require "sprockets/railtie" 16 | require "rails/test_unit/railtie" 17 | 18 | # Require the gems listed in Gemfile, including any gems 19 | # you've limited to :test, :development, or :production. 20 | Bundler.require(*Rails.groups) 21 | 22 | module Doppler 23 | class Application < Rails::Application 24 | # Initialize configuration defaults for originally generated Rails version. 25 | config.load_defaults 7.1 26 | 27 | # Configuration for the application, engines, and railties goes here. 28 | # 29 | # These settings can be overridden in specific environments using the files 30 | # in config/environments, which are processed later. 31 | # 32 | # config.time_zone = "Central Time (US & Canada)" 33 | # config.eager_load_paths << Rails.root.join("extras") 34 | config.eager_load_paths += Dir[Rails.root.join("lib")] 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/images.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Images 2 | 3 | ## Images API 4 | 5 | #### Retrieving Images 6 | 7 | Retrieve collections of images following the "images" link from [shows](/v2/docs/shows). 8 | 9 | ``` 10 | curl -v "#{ArtsyApi::V2.root}/images?show_id={show_id}" -H "X-XAPP-Token: XAPP_TOKEN" 11 | ``` 12 | 13 | This endpoint accepts the following parameters. 14 | 15 | Name | Value | 16 | ----------:|:------------------------------------------------------------------------------------| 17 | show_id | Retrieve images for a given [show](/v2/docs/shows), often called "installation shots". | 18 | 19 | #### Retrieving an Image 20 | 21 | Images are usually related to other objects, such as [profiles](/v2/docs/profiles). 22 | 23 | ``` 24 | curl -v "#{ArtsyApi::V2.root}/images/{id}" -H "X-XAPP-Token: XAPP_TOKEN" 25 | ``` 26 | 27 | ## Image JSON Format 28 | 29 | #{modelref://Image} 30 | 31 | #### Links 32 | 33 | Key | Target | 34 | ----------:|:--------------------------------------------------------| 35 | self | The image resource. | 36 | 37 | #### Example 38 | 39 | ``` json 40 | #{resource://image/id=54bfdb597261692b57fd0000} 41 | ``` 42 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/bids.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Bids 2 | 3 | ## Bids API 4 | 5 | An Bid is created while processing a [bidder position](/v2/docs/bidder_positions) (following that Auction's rules regarding increments, reserve pricing and so on). 6 | 7 | For more information about how Artsy's bidding logic and models, see our [blog post on the topic](http://artsy.github.io/blog/2014/04/17/building-an-english-auction-with-mongodb/). 8 | 9 | ``` alert[danger] 10 | The Bids API is restricted to authorized applications/users. 11 | ``` 12 | 13 | #### Retrieving a Bid 14 | 15 | Users can retrieve a specific bid by ID by rendering the "bid" link template from [root](#{ArtsyApi::V2.root}). 16 | 17 | ``` 18 | curl -v "#{ArtsyApi::V2.root}/bids/{id}" -H "X-XAPP-Token: XAPP_TOKEN" 19 | ``` 20 | 21 | ## Bid JSON Format 22 | 23 | #{modelref://Bid} 24 | 25 | #### Links 26 | 27 | Key | Target | 28 | ------------------:|:------------------------------------------------------| 29 | self | The bid resource. | 30 | position | The source [bidder position](docs/bidder_positions) | 31 | artwork | The associated [artwork](docs/artworks) | 32 | sale | The [sale](docs/sales) where the bid was placed | 33 | -------------------------------------------------------------------------------- /app/controllers/client_applications_controller.rb: -------------------------------------------------------------------------------- 1 | class ClientApplicationsController < ApplicationController 2 | include OpenRedirectHelper 3 | 4 | before_action :fetch_client_application, only: %i[show edit destroy update] 5 | before_action :no_cache!, except: [:index] 6 | before_action :parse_redirect_uris, only: %i[update] 7 | 8 | def show 9 | end 10 | 11 | def update 12 | @client_application._put(**client_application_params.to_h) 13 | fetch_client_application 14 | render :show 15 | end 16 | 17 | def index 18 | @client_applications = artsy_client.applications 19 | rescue => e 20 | @error = e.message 21 | end 22 | 23 | def destroy 24 | @client_application._delete 25 | redirect_to client_applications_path 26 | end 27 | 28 | private 29 | 30 | def fetch_client_application 31 | @client_application = artsy_client.application(id: params[:id]) 32 | return if @client_application 33 | 34 | flash[:error] = "Invalid application." 35 | redirect_to client_applications_path 36 | end 37 | 38 | def parse_redirect_uris 39 | return unless client_application_params[:redirect_urls] 40 | 41 | client_application_params[:redirect_urls] = client_application_params[:redirect_urls].split.compact.uniq 42 | end 43 | 44 | def client_application_params 45 | @client_application_params ||= params.require(:client_application).permit(:name, :redirect_urls, :redirect_uri) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This assumes you have general prerequisites installed as by: 4 | # https://github.com/artsy/potential/blob/main/scripts/setup 5 | 6 | # Exit if any subcommand fails 7 | set -e 8 | 9 | if command -v mise >/dev/null; then 10 | echo "Installing language dependencies with mise" 11 | mise install 12 | else 13 | echo "Skipping language dependencies installation (mise not found)" 14 | fi 15 | 16 | echo "Downloading .env.shared (for common local dev config)..." 17 | aws s3 cp s3://artsy-citadel/doppler/.env.shared ./ 18 | 19 | echo "Installing prerequisite gems..." 20 | gem update --system 21 | gem install foreman bundler 22 | if command -v rbenv >/dev/null; then rbenv rehash; fi 23 | 24 | 25 | if [ ! -e ".env" ]; then 26 | echo "Initializing .env from .env.example (for any custom configuration)..." 27 | cp .env.example .env 28 | fi 29 | 30 | echo "Installing gems..." 31 | if [[ "$(uname -m)" == "arm64" ]]; then 32 | echo "Detected ARM architecture. Ensuring native gems are installed for ARM64..." 33 | gem install nio4r -- --with-cflags="-arch arm64" 34 | fi 35 | 36 | bundle install 37 | 38 | echo " 39 | Done! 40 | 41 | Your local dev environment is setup with common config in .env.shared. You can override them in .env. 42 | 43 | You should be able to run some tests, as in: 44 | 45 | bundle exec rspec spec/requests 46 | 47 | To start a local server (at http://localhost:3000): 48 | 49 | foreman start 50 | " 51 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/bidder_positions.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Bidder Positions 2 | 3 | ## Bidder Positions API 4 | 5 | A bidder creates [bidder positions](/v2/docs/bidder_positions). 6 | 7 | ``` alert[danger] 8 | The Bidder Positions API is restricted to authorized applications/users. 9 | ``` 10 | 11 | #### Retrieving a Bidder Position 12 | 13 | Users can retrieve a specific bidder position by ID by rendering the "bidder_position" link template from [root](#{ArtsyApi::V2.root}). 14 | 15 | ``` 16 | curl -v "#{ArtsyApi::V2.root}/bidder_positions/{id}" -H "X-XAPP-Token: XAPP_TOKEN" 17 | ``` 18 | 19 | ## Bidder Position JSON Format 20 | 21 | #{modelref://BidderPosition} 22 | 23 | #### Links 24 | 25 | Key | Target | 26 | --------------------------------:|:--------------------------------------------------------------------| 27 | self | The bidder position resource. | 28 | sale\_artwork | The [sale artwork](docs/sale_artworks) of the bidder position. | 29 | sale | The [sale](/v2/docs/sales) of the bidder position. | 30 | artwork | The [artwork](/v2/docs/artworks) of the bidder position. | 31 | bidder | The [bidder](/v2/docs/bidders) of the bidder position. | 32 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | root "welcome#index" 3 | 4 | resources :client_applications, except: [:new] do 5 | resources :client_application_partners, only: [:index] 6 | resources :webhook_deliveries, only: [:index, :show] do 7 | member do 8 | post :redeliver 9 | end 10 | end 11 | end 12 | 13 | mount ArtsyAuth::Engine => "/" 14 | 15 | get "/health/ping", to: "health#ping" 16 | get "/docs", to: "pages#show", id: "docs" 17 | 18 | # Partner API 19 | 20 | get "/v1", to: "pages#show", id: "v1/index" 21 | get "/v1/playground", to: "playground#index" 22 | get "/v1/terms", to: "pages#show", id: "v1/terms" 23 | get "/v1/start", to: "v1/start#show" 24 | 25 | # Public API 26 | 27 | get "/v2", to: "pages#show", id: "v2/index" 28 | %i[ 29 | applications 30 | artists 31 | artworks 32 | authentication 33 | bidder_positions 34 | bidders 35 | bids 36 | collection_items 37 | collections 38 | docs 39 | editions 40 | errors 41 | fairs 42 | genes 43 | http 44 | images 45 | links 46 | markdown 47 | pagination 48 | partners 49 | profiles 50 | rate_limiting 51 | sales 52 | search 53 | shows 54 | status 55 | user_details 56 | users 57 | ].each do |page| 58 | get "/v2/docs/#{page}", to: "pages#show", id: "v2/docs/#{page}" 59 | end 60 | get "/v2/playground", to: "playground#index" 61 | get "/v2/terms", to: "pages#show", id: "v2/terms" 62 | 63 | get "/help", to: "help#show" 64 | end 65 | -------------------------------------------------------------------------------- /app/views/client_applications/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 My Apps 2 | 3 | - if !@client_applications 4 | 5 | .alert.alert-danger 6 | There was an error retrieving applications: #{@error}. 7 | 8 | - elsif @client_applications.any? 9 | 10 | %table.table.table-bordered.table-striped 11 | %thead 12 | %tr 13 | %th Name 14 | %th API Version 15 | %th Client ID 16 | %th Created 17 | %th Enabled 18 | %th   19 | 20 | %tbody.applications 21 | - @client_applications.each do |client_application| 22 | %tr 23 | %td.name= link_to client_application.name, client_application_path(client_application.id) 24 | %td.api_version= client_application.api_version 25 | %td.client_id= client_application.client_id 26 | %td.created_at= DateTime.parse(client_application.created_at).strftime("%B %d, %Y") 27 | %td.enabled 28 | = client_application.enabled ? 'Yes' : 'No' 29 | -if client_application.enabled && client_application.published_artworks_access_enabled 30 | + Published Artworks 31 | %td.actions 32 | = link_to 'edit', edit_client_application_path(client_application.id), class: 'btn btn-primary' 33 | // version 1 applications are typically production and are protected in Gravity 34 | = link_to 'destroy', client_application_path(client_application.id), disabled: client_application.api_version == 1, method: :delete, data: { confirm: "Are you sure?" }, class: 'btn btn-danger' 35 | - else 36 | 37 | .alert.alert-info 38 | No apps found 39 | -------------------------------------------------------------------------------- /app/views/webhook_deliveries/show.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Webhook Delivery Details 2 | 3 | = link_to "Back to Webhook List", client_application_webhook_deliveries_path(params[:client_application_id]), class: 'btn' 4 | - if @error.present? 5 | .alert.alert-warning 6 | Something went wrong! 7 | - else 8 | %table.table.table-bordered 9 | %tr 10 | %th Event Name 11 | %td= @webhook_delivery.webhook_event.name 12 | 13 | %tr 14 | %th Response Status 15 | %td{ class: status_class(@webhook_delivery.response_status) }= @webhook_delivery.response_status 16 | 17 | %tr 18 | %th Error Class 19 | %td= @webhook_delivery.error_class || "None" 20 | 21 | %tr 22 | %th Created At 23 | %td= @webhook_delivery.created_at && DateTime.parse(@webhook_delivery.created_at).strftime("%B %d, %Y") 24 | 25 | %tr 26 | %th Completed At 27 | %td= @webhook_delivery.completed_at && DateTime.parse(@webhook_delivery.completed_at).strftime("%B %d, %Y") 28 | 29 | 30 | %tr 31 | %th Webhook ID 32 | %td= @webhook_delivery.webhook_id 33 | 34 | %tr 35 | %th Configured Webhook Endpoint URL 36 | %td= @webhook_delivery.webhook_url 37 | 38 | %tr 39 | %th Redeliver 40 | %td.actions 41 | = link_to 'Redeliver', redeliver_client_application_webhook_delivery_path(params[:client_application_id], @webhook_delivery.id), method: :post, data: { confirm: "Are you sure?" }, class: 'btn btn-primary' 42 | 43 | 44 | 45 | %h2 Webhook Event Payload 46 | 47 | %pre 48 | %code 49 | = JSON.pretty_generate(@webhook_delivery.webhook_event.as_json) 50 | 51 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS", 5) 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 11 | # 12 | port ENV.fetch("PORT", 3000) 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV", "development") 17 | 18 | # Specifies the `pidfile` that Puma will use. 19 | # pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 20 | 21 | # Specifies the number of `workers` to boot in clustered mode. 22 | # Workers are forked webserver processes. If using threads and workers together 23 | # the concurrency of the application would be max `threads` * `workers`. 24 | # Workers do not work on JRuby or Windows (both of which do not support 25 | # processes). 26 | # 27 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 28 | 29 | # Use the `preload_app!` method when specifying a `workers` number. 30 | # This directive tells Puma to first boot the application and load code 31 | # before forking the application. This takes advantage of Copy On Write 32 | # process behavior so workers use less memory. 33 | # 34 | # preload_app! 35 | 36 | # Allow puma to be restarted by `rails restart` command. 37 | plugin :tmp_restart 38 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/markdown.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > General > Markdown Content 2 | 3 | ## Markdown Content 4 | 5 | Many content fields in the Artsy API, such as [artist](/v2/docs/artists) bios or [gene](/v2/docs/genes) descriptions, will return data in [markdown format](http://daringfireball.net/projects/markdown/syntax). For example, the description of the "Pop Art" gene includes the following. 6 | 7 | ``` 8 | **“The Pop artists did images that anybody walking down Broadway could recognize in a split second—comics, picnic tables, men’s trousers, celebrities, shower curtains, refrigerators, coke bottles—all the great modern things that the Abstract Expressionists tried so hard not to notice at all.” – [Andy Warhol](/artist/andy-warhol)** 9 | ``` 10 | 11 | Once rendered, this becomes a quoted bold text. 12 | 13 | **“The Pop artists did images that anybody walking down Broadway could recognize in a split second—comics, picnic tables, men’s trousers, celebrities, shower curtains, refrigerators, coke bottles—all the great modern things that the Abstract Expressionists tried so hard not to notice at all.” – [Andy Warhol](https://artsy.net/artist/andy-warhol)** 14 | 15 | ``` alert[info] 16 | Some internal links within the markdown content may be relative to https://artsy.net. 17 | ``` 18 | 19 | ``` alert[danger] 20 | While rich user-generated content on Artsy is often parsed, sanitized and converted to markdown in the API output, you should never trust any data returned by the API to be safe in your application. Fields that don't accept rich content (eg. artwork titles) will store user's input "as is" without any cleanup. 21 | ``` 22 | -------------------------------------------------------------------------------- /vendor/assets/swagger-ui-dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swagger UI 7 | 8 | 9 | 10 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/fairs.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Fairs 2 | 3 | ## Fairs API 4 | 5 | An art fair is an event to which [partners](/v2/docs/partners) bring [shows](/v2/docs/shows) with [artworks](/v2/docs/artworks). 6 | 7 | #### Retrieving Fairs 8 | 9 | Retrieve fairs by following the [fairs](#{ArtsyApi::V2.root}/fairs) link from [root](#{ArtsyApi::V2.root}). 10 | 11 | ``` 12 | curl -v "#{ArtsyApi::V2.root}/fairs" -H "X-XAPP-Token: XAPP_TOKEN" 13 | ``` 14 | 15 | This endpoint accepts the following parameters. 16 | 17 | Name | Value | 18 | ----------:|:------------------------------------------------------------------------------------------| 19 | status | One of 'running', 'closed', 'upcoming' or 'current'. | 20 | 21 | The response is a [paginated result](/v2/docs/pagination) with embedded fairs. 22 | 23 | #### Retrieving a Fair 24 | 25 | Users can retrieve a specific fair by ID by rendering the "fair" link template from [root](#{ArtsyApi::V2.root}). 26 | 27 | ``` 28 | curl -v "#{ArtsyApi::V2.root}/fairs/{id}" -H "X-XAPP-Token: XAPP_TOKEN" 29 | ``` 30 | 31 | ## Fair JSON Format 32 | 33 | #{modelref://Fair} 34 | 35 | #### Links 36 | 37 | Key | Target | 38 | ----------:|:------------------------------------------------| 39 | self | The fair resource. | 40 | shows | Fair [shows](/v2/docs/shows). | 41 | 42 | #### Example 43 | 44 | ``` json 45 | #{resource://fair/id=543865d4726169329c350300} 46 | ``` 47 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/profiles.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Profiles 2 | 3 | ## Profiles API 4 | 5 | A public profile provides information about a [partner](/v2/docs/partners) or a [user](/v2/docs/users). 6 | 7 | #### Retrieving Profiles 8 | 9 | Retrieve profiles by following the [profiles](#{ArtsyApi::V2.root}/profiles) link from [root](#{ArtsyApi::V2.root}). 10 | 11 | ``` 12 | curl -v "#{ArtsyApi::V2.root}/profiles" -H "X-XAPP-Token: XAPP_TOKEN" 13 | ``` 14 | 15 | The response is a [paginated result](/v2/docs/pagination) with embedded profiles. 16 | 17 | #### Retrieving a Profile 18 | 19 | Users can retrieve a specific profile by ID by rendering the "profile" link template from [root](#{ArtsyApi::V2.root}). 20 | 21 | ``` 22 | curl -v "#{ArtsyApi::V2.root}/profiles/{id}" -H "X-XAPP-Token: XAPP_TOKEN" 23 | ``` 24 | 25 | ## Profile JSON Format 26 | 27 | #{modelref://Profile} 28 | 29 | #### Links 30 | 31 | Key | Target | 32 | -----------:|:----------------------------------------------------------------| 33 | self | The profile resource. | 34 | permalink | An external location on the artsy.net website. | 35 | owner | Profile owner ([user](/v2/docs/users), [fair](/v2/docs/fairs), etc). | 36 | cover_image | A cover [image](/v2/docs/images). | 37 | image:self | Curied image location. | 38 | website | Official website. | 39 | 40 | #### Example 41 | 42 | ``` json 43 | #{resource://profile/id=5159dac69a608324390001e5} 44 | ``` 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Doppler 2 | [![CircleCI](https://circleci.com/gh/artsy/doppler.svg?style=svg)](https://circleci.com/gh/artsy/doppler) 3 | [![Code Climate](https://codeclimate.com/github/artsy/doppler.svg)](https://codeclimate.com/github/artsy/doppler) 4 | 5 | I am Doppler, the Artsy developer website. 6 | 7 | - **Production:** [developers.artsy.net](https://developers.artsy.net) 8 | - **Staging:** [developers-staging.artsy.net/](https://developers-staging.artsy.net/) 9 | 10 | --- 11 | 12 | ### Public API Retirement Notice 13 | 14 | Please note, we are in the process of retiring the [public api.](developers.artsy.net/v2). 15 | 16 | The public api, as well as its documentation and playground, may be taken down at any time without additional notice. 17 | 18 | This change will not affect partners currently using our [partner api.](developers.artsy.net/v1). 19 | 20 | If you're interested in integrating your service with Artsy's partner api, please reach out to .... 21 | 22 | --- 23 | 24 | ### Setup 25 | 26 | Clone the project: 27 | 28 | ``` 29 | $ git clone git@github.com:artsy/doppler.git 30 | ``` 31 | 32 | Review the setup script and run it _or the equivalent steps for your environment_: 33 | 34 | ```bash 35 | $ cat bin/setup 36 | $ bin/setup 37 | ``` 38 | 39 | Then it should be possibe to run tests: 40 | 41 | ```bash 42 | bundle exec rspec 43 | ``` 44 | 45 | ...or start the application (at http://localhost:3000): 46 | 47 | ```bash 48 | foreman start 49 | ``` 50 | 51 | ### Contributing 52 | 53 | We welcome contributions. See [CONTRIBUTING](CONTRIBUTING.md) and [DEV](DEV.md). 54 | 55 | ### Copyright & License 56 | 57 | MIT License, see [LICENSE](LICENSE) for details. 58 | 59 | (c) 2014-2018 Artsy Inc. and Contributors 60 | -------------------------------------------------------------------------------- /app/views/client_application_partners/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 My Configured Partners 2 | 3 | = link_to "Back to App", client_application_path(params[:client_application_id]), class: 'btn' 4 | - if !@client_application_partners.any? 5 | .alert.alert-warning 6 | There are no configured partners for this application. Please reach out to your Artsy representative to configure partners. 7 | 8 | - elsif @client_application_partners.any? 9 | %table.table.table-bordered.table-striped 10 | %thead 11 | %tr 12 | %th ID 13 | %th Client App Name 14 | %th Client App ID 15 | %th Partner Name 16 | %th Partner ID 17 | %th Created 18 | %th Last Updated 19 | 20 | %tbody.applications 21 | - @client_application_partners.each do |client_application_partner| 22 | %tr 23 | %td.id= client_application_partner.id 24 | %td.client_application_name 25 | = link_to client_application_partner.client_application_name, client_application_path(client_application_partner.client_application_id) 26 | %td.client_application_id= client_application_partner.client_application_id 27 | %td.partner_name= client_application_partner.partner_name 28 | %td.partner_id= client_application_partner.partner_id 29 | %td.created_at= DateTime.parse(client_application_partner.created_at).strftime("%B %d, %Y") 30 | %td.updated_at= DateTime.parse(client_application_partner.updated_at).strftime("%B %d, %Y") 31 | 32 | 33 | = link_to 'Previous', client_application_webhook_deliveries_path(page: @current_page - 1) if @current_page > 1 34 | = link_to 'Next', client_application_webhook_deliveries_path(page: @current_page + 1) if @current_page < @total_pages 35 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/sales.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Sales 2 | 3 | ## Sales API 4 | 5 | Sales and Auctions are available at the sales endpoint. 6 | 7 | #### Retrieving Sales 8 | 9 | Users can retrieve sales by following the [sales](#{ArtsyApi::V2.root}/sales) link from [root](#{ArtsyApi::V2.root}). 10 | 11 | ``` 12 | curl -v "#{ArtsyApi::V2.root}/sales" -H "X-Access-Token: ACCESS_TOKEN" 13 | ``` 14 | 15 | This endpoint accepts the following parameters. 16 | 17 | Name | Value | 18 | ----------:|:---------------------------------------| 19 | live | Boolean- filter live auctions only | 20 | is_auction | Boolean- filter auctions only | 21 | published | Boolean- limit to published sales | 22 | 23 | ``` alert[warning] 24 | Only authorized users/applications may access **unpublished** sales= 25 | ``` 26 | 27 | The response is a [paginated result](/v2/docs/pagination) with embedded sales. 28 | 29 | ``` json 30 | { 31 | "total_count" : 1, 32 | "_links" : { 33 | "self" : { 34 | "href" : "#{ArtsyApi::V2.root}/sales?live=..." 35 | }, 36 | "next" : { 37 | "href" : "#{ArtsyApi::V2.root}/sales?cursor=...&live=..." 38 | } 39 | }, 40 | "_embedded" : { 41 | "sales" : [ 42 | { 43 | "id":"...", 44 | ... 45 | } 46 | ] 47 | } 48 | } 49 | ``` 50 | 51 | #### Retrieving a Sale 52 | 53 | Users can retrieve a specific sale by ID by rendering the "sale" link template from [root](#{ArtsyApi::V2.root}). 54 | 55 | ``` 56 | curl -v "#{ArtsyApi::V2.root}/sales/{id}" -H "X-XAPP-Token: XAPP_TOKEN" 57 | ``` 58 | 59 | ## Sale JSON Format 60 | 61 | #{modelref://Sale} 62 | 63 | #### Example 64 | 65 | ``` json 66 | #{resource://sale/id=58126d67cd530e21c40002ec} 67 | ``` 68 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/errors.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > General > Errors 2 | 3 | ## Errors 4 | 5 | ### 400 Errors 6 | 7 | The API returns errors in JSON format along with HTTP 40x status codes. 8 | 9 | #### Error Fields 10 | 11 | Key | Description | 12 | -------------:|:---------------------------------------------------| 13 | type | Error type. | 14 | message | Humanly readable error message. | 15 | detail | Additional detail specific to an error type. | 16 | 17 | #### Examples 18 | 19 | An access denied error. 20 | 21 | ``` json 22 | { 23 | "type" : "auth_error", 24 | "message" : "The access token is invalid or has expired." 25 | } 26 | ``` 27 | 28 | An artist not found error. 29 | 30 | ``` json 31 | { 32 | "type" : "other_error", 33 | "message" : "Artist Not Found" 34 | } 35 | ``` 36 | 37 | A parameter validation error. 38 | 39 | ``` json 40 | { 41 | "type" : "param_error", 42 | "message" : "Email can't be blank, Email is invalid, Password can't be blank.", 43 | "detail" : { 44 | "email" : ["can't be blank", "is invalid"], 45 | "password" : ["can't be blank"] 46 | } 47 | } 48 | ``` 49 | 50 | ### 429 Errors 51 | 52 | The API will return an HTTP 429 `Too many requests` error when your client is over the rate limit. See [Rate Limiting](/v2/docs/rate_limiting) for more information. 53 | 54 | ### 500 Errors 55 | 56 | Internal server errors in the 50x range are not expected. Please [report](/help) any 50x errors. 57 | 58 | ### 401 Broadway 59 | 60 | The API has an easter egg, we'd love it for you to find it! You can get `401 Broadway`, which is the address of the [Artsy HQ in NYC](http://artsy.net/about), instead of `401 Unauthorized`. Happy hunting. 61 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/links.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > General > Links Between Resources 2 | 3 | ## Links Between Resources 4 | 5 | Links in HAL responses are contained directly within a resource. Thet are represented as a JSON object contained within a "\_links" hash. 6 | 7 | ``` json 8 | { 9 | "_links" : { 10 | "next" : { 11 | "href": "#{ArtsyApi::V2.root}/..." 12 | } 13 | } 14 | } 15 | ``` 16 | 17 | #### API Discoverability 18 | 19 | A HAL API should be navigated from its [root](#{ArtsyApi::V2.root}). The root itself is a collection of links. 20 | 21 | #### Link Relations 22 | 23 | Links have a relation, aka. "rel". This indicates the semantic, the meaning, of a particular link. Link rels are the main way of distinguishing between a resource's links. It's basically just a key within the "\_links" hash, associating the link meaning (the 'rel') with the link object that contains data like the actual "href" value: 24 | 25 | ``` json 26 | { 27 | "_links" : { 28 | "artworks" : { 29 | "href": "#{ArtsyApi::V2.root}/..." 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | The "rel" above is "artworks" and can be typically found in an [artist](/v2/docs/artists) JSON object. Link rels are URLs which reveal documentation about the given link, making them "discoverable". 36 | 37 | #### Templated Links 38 | 39 | Templated links have a "templated" attribute set to "true". 40 | 41 | ```json 42 | "_links" : 43 | "artist" : { 44 | "href" : "#{ArtsyApi::V2.root}/artists/{id}", 45 | "templated" : true 46 | } 47 | } 48 | ``` 49 | 50 | Query string parameters are declared as follows. 51 | 52 | ```json 53 | "_links" : 54 | "artist" : { 55 | "href" : "#{ArtsyApi::V2.root}/artworks{?public,artist_id}", 56 | "templated" : true 57 | } 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /spec/helpers/markdown_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe MarkdownHelper do 4 | subject do 5 | Class.new do 6 | extend MarkdownHelper 7 | end 8 | end 9 | context "#render_markdown" do 10 | it "minimal document" do 11 | expect(subject.render_markdown("")).to eq "" 12 | expect(subject.render_markdown("hello world")).to eq "

hello world

\n" 13 | end 14 | it "table markup" do 15 | expect(subject.render_markdown(<<~TEXT 16 | x | y | 17 | --:|:--| 18 | a | b | 19 | TEXT 20 | )).to start_with '' 21 | end 22 | it "code markup" do 23 | expect(subject.render_markdown(<<~TEXT 24 | ``` 25 | code 26 | ``` 27 | TEXT 28 | )).to eq "
code\n
\n" 29 | end 30 | it "ruby markup" do 31 | expect(subject.render_markdown(<<~TEXT 32 | ``` ruby 33 | x = y 34 | ``` 35 | TEXT 36 | )).to eq "
\n
x = y
\n
\n" 37 | end 38 | it "alert markup" do 39 | expect(subject.render_markdown(<<~TEXT 40 | ``` alert[warning] 41 | WARNING! 42 | ``` 43 | TEXT 44 | )).to eq "
\n

WARNING!

\n
\n" 45 | end 46 | it "alert markup with markdown" do 47 | expect(subject.render_markdown(<<~TEXT 48 | ``` alert[warning] 49 | [docs](/docs) 50 | ``` 51 | TEXT 52 | )).to eq "
\n

docs

\n
\n" 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /app/views/webhook_deliveries/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Webhook Deliveries 2 | 3 | = link_to "Back to App", client_application_path(params[:client_application_id]), class: 'btn' 4 | - if !@webhook_deliveries.any? 5 | .alert.alert-warning 6 | There are no attempted webhook delivery events 7 | 8 | - elsif @webhook_deliveries.any? 9 | %table.table.table-bordered.table-striped 10 | %thead 11 | %tr 12 | %th ID 13 | %th Event Name 14 | %th Response Status 15 | %th Error Class 16 | %th Created At 17 | %th Completed At 18 | %th   19 | 20 | %tbody.applications 21 | - @webhook_deliveries.each do |webhook_delivery| 22 | %tr 23 | %td.id= link_to webhook_delivery.id, client_application_webhook_delivery_path(params[:client_application_id], webhook_delivery.id) 24 | %td.event_name= webhook_delivery.webhook_event.name 25 | %td.response_status{ class: status_class(webhook_delivery.response_status) }= webhook_delivery.response_status 26 | %td.error_class= webhook_delivery.error_class 27 | %td.created_at= webhook_delivery.created_at && DateTime.parse(webhook_delivery.created_at).strftime("%B %d, %Y") 28 | %td.completed_at= webhook_delivery.completed_at && DateTime.parse(webhook_delivery.completed_at).strftime("%B %d, %Y") 29 | %td.actions 30 | = link_to 'Redeliver', redeliver_client_application_webhook_delivery_path(params[:client_application_id], webhook_delivery.id), method: :post, data: { confirm: "Are you sure?" }, class: 'btn btn-primary' 31 | 32 | 33 | 34 | = link_to 'Previous', client_application_webhook_deliveries_path(page: @current_page - 1) if @current_page > 1 35 | = link_to 'Next', client_application_webhook_deliveries_path(page: @current_page + 1) if @current_page < @total_pages 36 | 37 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/users.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Users 2 | 3 | ``` alert[info] 4 | This API endpoint requires a valid [user access token](/v2/docs/authentication). 5 | ``` 6 | 7 | ## Users API 8 | 9 | A user represents a registered account on Artsy. 10 | 11 | #### Retrieving a User 12 | 13 | Users can retrieve a specific user by rendering the "user" link template from [root](#{ArtsyApi::V2.root}). 14 | 15 | ``` 16 | curl -v "#{ArtsyApi::V2.root}/users/{id}" -H "X-Access-Token: ACCESS_TOKEN" 17 | ``` 18 | 19 | #### Retrieving Current User 20 | 21 | Retrieve the currently authenticated user by following the [current_user](#{ArtsyApi::V2.root}/current_user) link from [root](#{ArtsyApi::V2.root}). 22 | 23 | ``` 24 | curl -v "#{ArtsyApi::V2.root}/current_user" -H "X-Access-Token: ACCESS_TOKEN" 25 | ``` 26 | 27 | The response will be a 302 redirect to a "users" link with the current user ID. 28 | 29 | ## User JSON Format 30 | 31 | #{modelref://User} 32 | 33 | #### Links 34 | 35 | Key | Target | 36 | ----------:|:-----------------------------------------------------| 37 | self | The profile resource. | 38 | profile | Link to the user's [public profile](/v2/docs/profiles). | 39 | user_details | Link to user's [details](/v2/docs/user_details). | 40 | 41 | #### Example 42 | 43 | ``` json 44 | 45 | { 46 | "id" : "52fe4b28c94d114d36000001", 47 | "name" : "Joe Person", 48 | "_links" : { 49 | "self" : { 50 | "href" : "#{ArtsyApi::V2.root}/users/52fe4b28c94d114d36000001" 51 | }, 52 | "profile" : { 53 | "href" : "http://localhost:3000/api/profiles/52fe4b2ac94d114d36000005" 54 | }, 55 | "user_details" : { 56 | "href" : "http://localhost:3000/api/user_details/52fe4b28c94d114d36000001" 57 | } 58 | } 59 | } 60 | ``` 61 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/user_details.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > User Details 2 | 3 | ``` alert[info] 4 | This User Details API is restricted to authorized applications and users with a valid [user access token](/v2/docs/authentication). 5 | ``` 6 | 7 | ## User Details API 8 | 9 | While a [user](/v2/docs/users) represents publicly available reference about a registered user account on Artsy, user details are the private portion of such information, including the user's e-mail address or phone number. 10 | 11 | #### Retrieving User Details 12 | 13 | Users can retrieve a specific user by following the "user_details" link from a [user](/v2/docs/users). 14 | 15 | ``` 16 | curl -v "#{ArtsyApi::V2.root}/user_details/{id}" -H "X-Access-Token: ACCESS_TOKEN" 17 | ``` 18 | 19 | ## User Details JSON Format 20 | 21 | #{modelref://UserDetail} 22 | 23 | #### Links 24 | 25 | Key | Target | 26 | ----------:|:------------------------------------------------------------------------| 27 | self | The profile resource. | 28 | user | Link to the [user](/v2/docs/users) information. | 29 | partners | Link to [partners](/v2/docs/partners) this user has management access to. | 30 | 31 | #### Example 32 | 33 | ``` json 34 | { 35 | "id" : "52fe4b28c94d114d36000001", 36 | "created_at" : "2014-02-14T16:58:17+00:00", 37 | "updated_at" : "2014-09-08T13:32:34+00:00", 38 | "type" : "User", 39 | "email" : "user@example.com", 40 | "birthday" : null, 41 | "phone" : null, 42 | "gender" : "female", 43 | "_links" : { 44 | "self" : { 45 | "href" : "#{ArtsyApi::V2.root}/user_details/52fe4b28c94d114d36000001" 46 | }, 47 | "user" : { 48 | "href" : "#{ArtsyApi::V2.root}/user/52fe4b28c94d114d36000001" 49 | } 50 | } 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /app/views/client_applications/edit.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Edit App 2 | 3 | = link_to "Back to Apps", client_applications_path, class: 'btn' 4 | 5 | - if @client_application.enabled && @client_application.published_artworks_access_enabled 6 | .alert.alert-success 7 | This application has access to all published artworks. 8 | 9 | = bootstrap_form_for ClientApplication.new(@client_application._attributes.to_h), method: 'PUT' do |f| 10 | %table.table.table-bordered.table-striped 11 | %tbody#application 12 | %tr 13 | %th.text-right Name 14 | %td= f.text_field :name, hide_label: true, required: true, value: @client_application.name, placeholder: 'This can be any name that you would recognize.' 15 | %tr 16 | %th.text-right API Version 17 | %td#version= @client_application.api_version 18 | %tr 19 | %th.text-right Client Id 20 | %td#client_id= @client_application.client_id 21 | %tr 22 | %th.text-right Client Secret 23 | %td#client_secret= @client_application.client_secret 24 | %tr 25 | %th.text-right Redirect URLs 26 | %td#redirect_urls= f.text_area :redirect_urls, hide_label: true, rows: 5, value: (@client_application.redirect_urls || []).join("\n"), placeholder: 'The location(s) of your application for OAuth workflow.' 27 | %tr 28 | %th.text-right Created 29 | %td#created_at= DateTime.parse(@client_application.created_at).strftime("%B %d, %Y %H:%M") 30 | %tr 31 | %th.text-right Updated 32 | %td#updated_at= DateTime.parse(@client_application.updated_at).strftime("%B %d, %Y %H:%M") 33 | %tr 34 | %th.text-right Enabled 35 | %td#enabled= @client_application.enabled ? 'Yes' : 'No' 36 | -if @client_application.roles && @client_application.roles.any? 37 | %tr 38 | %th.text-right Roles 39 | %td#roles= @client_application.roles.join(', ') 40 | 41 | = f.primary 'Save' 42 | = link_to "Cancel", "javascript:history.back()", class: 'btn btn-default' 43 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/genes.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Genes 2 | 3 | ## Genes API 4 | 5 | Think about an art object, say a painting by Andy Warhol. You might say it is a painting, that it is a work of Pop Art, that it is a silkscreen, that it features an image of Marilyn Monroe, that it is very high contrast, or even that it emphasizes the flatness of the image. These characteristics or terms (e.g. "Pop Art", "Flatness", "Bright Colors") are what we call genes. 6 | 7 | #### Retrieving Genes 8 | 9 | Retrieve genes by following the [genes](#{ArtsyApi::V2.root}/genes) link from [root](#{ArtsyApi::V2.root}), or the "genes" links within an [artwork](/v2/docs/artworks) or [artist](/v2/docs/artists). 10 | 11 | ``` 12 | curl -v "#{ArtsyApi::V2.root}/genes/{id}" -H "X-XAPP-Token: XAPP_TOKEN" 13 | ``` 14 | 15 | This endpoint accepts the following parameters. 16 | 17 | Name | Value | 18 | ----------:|:------------------------------------------------------------------| 19 | artist_id | Retrieve genes for a given [artist](/v2/docs/artists). | 20 | artwork_id | Retrieve genes for a given [artwork](/v2/docs/artworks). | 21 | 22 | ## Gene JSON Format 23 | 24 | #{modelref://Gene} 25 | 26 | #### Links 27 | 28 | Key | Target | 29 | ------------------:|:----------------------------------------------------------------| 30 | self | The gene resource. | 31 | thumbnail | Default image thumbnail. | 32 | image | Curied image location. | 33 | permalink | An external location on the artsy.net website. | 34 | artists | All [artists](/v2/docs/artists) that have this gene. | 35 | artworks | Public domain [artworks](/v2/docs/artworks) that have this gene. | 36 | published_artworks | All published [artworks](/v2/docs/artworks) that have this gene. | 37 | 38 | #### Example 39 | 40 | ``` json 41 | #{resource://gene/id=4e5e41670d2c670001030350} 42 | ``` 43 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/shows.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Shows 2 | 3 | ## Shows API 4 | 5 | A show is a display of [artworks](/v2/docs/artworks) by one or several [artists](/v2/docs/artists), produced by an Artsy [partner](/v2/docs/partners). 6 | 7 | #### Retrieving Shows 8 | 9 | Retrieve shows by following the [shows](#{ArtsyApi::V2.root}/shows) link from [root](#{ArtsyApi::V2.root}), or the "shows" links within a [partner](/v2/docs/partners). 10 | 11 | ``` 12 | curl -v "#{ArtsyApi::V2.root}/shows/{id}" -H "X-XAPP-Token: XAPP_TOKEN" 13 | ``` 14 | 15 | This endpoint accepts the following parameters. 16 | 17 | Name | Value | 18 | ----------:|:------------------------------------------------------------------| 19 | partner_id | Retrieve shows for a given [partner](/v2/docs/partners). | 20 | status | Retrieve shows with a specific status (see below). | 21 | fair_id | Retrieve shows for a given [fair](/v2/docs/fairs). | 22 | 23 | #### Show Status 24 | 25 | Status | Description | 26 | -------------:|:---------------------------------------------------| 27 | upcoming | Opening in the future. | 28 | running | Currently running. | 29 | closed | Closed. | 30 | current | Running or upcoming. | 31 | 32 | ## Show JSON Format 33 | 34 | #{modelref://Show} 35 | 36 | #### Links 37 | 38 | Key | Target | 39 | ----------:|:------------------------------------------------| 40 | self | The show resource. | 41 | thumbnail | Default image thumbnail. | 42 | image | Curied image location. | 43 | permalink | An external location on the artsy.net website. | 44 | partner | Show [partner](/v2/docs/partners). | 45 | artworks | Show [artworks](/v2/docs/artworks). | 46 | 47 | #### Example 48 | 49 | ``` json 50 | #{resource://show/id=4ea19ee97bab1a0001001908} 51 | ``` 52 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/partners.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Partners 2 | 3 | ## Partners API 4 | 5 | A partner provides [artworks](/v2/docs/artworks). 6 | 7 | #### Retrieving Partners 8 | 9 | Retrieve partners by following the [partners](#{ArtsyApi::V2.root}/partners) link from [root](#{ArtsyApi::V2.root}). 10 | 11 | ``` 12 | curl -v "#{ArtsyApi::V2.root}/partners" -H "X-XAPP-Token: XAPP_TOKEN" 13 | ``` 14 | 15 | This endpoint accepts the following parameters. 16 | 17 | Name | Value | 18 | ----------:|:------------------------------------------------------------------------------------------| 19 | user_id | Retrieve partners that a [user](/v2/docs/users) has access to. | 20 | partner_id | Combine with 'user_id' to check whether a [user](/v2/docs/users) has access to this partner. | 21 | 22 | The response is a [paginated result](/v2/docs/pagination) with embedded partners. 23 | 24 | #### Retrieving a Partner 25 | 26 | Users can retrieve a specific partner by ID by rendering the "partner" link template from [root](#{ArtsyApi::V2.root}). 27 | 28 | ``` 29 | curl -v "#{ArtsyApi::V2.root}/partners/{id}" -H "X-XAPP-Token: XAPP_TOKEN" 30 | ``` 31 | 32 | ## Partner JSON Format 33 | 34 | #{modelref://Partner} 35 | 36 | #### Links 37 | 38 | Key | Target | 39 | ------------------:|:--------------------------------------------------| 40 | self | The partner resource. | 41 | profile | Partner [profile](/v2/docs/profiles). | 42 | permalink | An external location on the artsy.net website. | 43 | website | Official website. | 44 | artworks | Partner [artworks](/v2/docs/artworks). | 45 | artists | Partner [artists](/v2/docs/artists). | 46 | published_artworks | All published partner [artworks](/v2/docs/artworks). | 47 | shows | Partner [shows](/v2/docs/shows). | 48 | 49 | #### Example 50 | 51 | ``` json 52 | #{resource://partner/id=4f99c7b793ab4b0001000179} 53 | ``` 54 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/bidders.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Bidders 2 | 3 | ## Bidders API 4 | 5 | A bidder is a [user](/v2/docs/users) registered for a specific [sale](/v2/docs/sales). 6 | 7 | ``` alert[danger] 8 | The Bidders API is restricted to authorized applications/users. 9 | ``` 10 | 11 | #### Retrieving Bidders 12 | 13 | Users can retrieve bidders by following the [bidders](#{ArtsyApi::V2.root}/bidders) link from [root](#{ArtsyApi::V2.root}). 14 | 15 | ``` 16 | curl -v "#{ArtsyApi::V2.root}/bidders?user_id=USER_ID" -H "X-Access-Token: ACCESS_TOKEN" 17 | ``` 18 | 19 | This endpoint accepts the following parameters. 20 | 21 | Name | Value | 22 | ----------:|:-------------------------------| 23 | sale_id | Applicable Auction (sale) ID. | 24 | 25 | The response is a [paginated result](/v2/docs/pagination) with embedded bidders. 26 | 27 | ``` json 28 | { 29 | "total_count" : 1, 30 | "_links" : { 31 | "self" : { 32 | "href" : "#{ArtsyApi::V2.root}/bidders?sale_id=..." 33 | }, 34 | "next" : { 35 | "href" : "#{ArtsyApi::V2.root}/bidders?cursor=...&sale_id=..." 36 | } 37 | }, 38 | "_embedded" : { 39 | "bidders" : [ 40 | { 41 | "id":"...", 42 | ... 43 | } 44 | ] 45 | } 46 | } 47 | ``` 48 | 49 | A request without a `sale_id` will receive a paginated list of all bidders. 50 | 51 | #### Retrieving a Bidder 52 | 53 | Authorized users and applications can retrieve a specific bidder by ID by rendering the "bidder" link template from [root](#{ArtsyApi::V2.root}). 54 | 55 | ``` 56 | curl -v "#{ArtsyApi::V2.root}/bidders/{id}" -H "X-XAPP-Token: XAPP_TOKEN" 57 | ``` 58 | 59 | ## Bidder JSON Format 60 | 61 | #{modelref://Bidder} 62 | 63 | #### Links 64 | 65 | Key | Target | 66 | --------------------------------:|:--------------------------------------------------------------------| 67 | self | The bidder resource. | 68 | sale | The [sale](/v2/docs/sales) of the bidder | 69 | user | The [user](/v2/docs/users) of the bidder | 70 | user_detail | The [user detail](/v2/docs/user_details) of the bidder | 71 | -------------------------------------------------------------------------------- /app/views/client_applications/show.html.haml: -------------------------------------------------------------------------------- 1 | %h1 App 2 | 3 | = link_to "Back to Apps", client_applications_path, class: 'btn' 4 | 5 | - if @client_application.enabled && @client_application.published_artworks_access_enabled 6 | .alert.alert-success 7 | This application has access to all published artworks. 8 | 9 | %table.table.table-bordered.table-striped 10 | %tbody#application 11 | %tr 12 | %th.text-right Name 13 | %td#name= @client_application.name 14 | %tr 15 | %th.text-right API Version 16 | %td#api_version= @client_application.api_version 17 | %tr 18 | %th.text-right Client Id 19 | %td#client_id= @client_application.client_id 20 | %tr 21 | %th.text-right Client Secret 22 | %td#client_secret= @client_application.client_secret 23 | %tr 24 | %th.text-right Redirect URLs 25 | %td#redirect_urls 26 | -if @client_application.redirect_urls && @client_application.redirect_urls.any? 27 | %pre= @client_application.redirect_urls.join("\n") 28 | -else 29 | None 30 | %tr 31 | %th.text-right Created 32 | %td#created_at= DateTime.parse(@client_application.created_at).strftime("%B %d, %Y %H:%M") 33 | %tr 34 | %th.text-right Updated 35 | %td#updated_at= DateTime.parse(@client_application.updated_at).strftime("%B %d, %Y %H:%M") 36 | %tr 37 | %th.text-right Enabled 38 | %td#enabled= @client_application.enabled ? 'Yes' : 'No' 39 | -if @client_application.roles && @client_application.roles.any? 40 | %tr 41 | %th.text-right Roles 42 | %td#roles= @client_application.roles.join(', ') 43 | -if @client_application.api_version == 1 44 | %tr 45 | %th.text-right Configured Application Partners 46 | %td#client_application_partners 47 | = link_to "Link", client_application_client_application_partners_path(@client_application.id) 48 | %tr 49 | %th.text-right Webhook Deliveries 50 | %td#client_application_partners 51 | = link_to "Link", client_application_webhook_deliveries_path(@client_application.id) 52 | 53 | = link_to 'Edit', edit_client_application_path(@client_application.id), class: 'btn btn-default' 54 | // version 1 applications are typically production and are protected in Gravity 55 | = link_to 'Destroy', client_application_path(@client_application.id), disabled: @client_application.api_version == 1, method: :delete, confirm: "Are you sure?", class: 'btn btn-danger' 56 | -------------------------------------------------------------------------------- /vendor/assets/swagger-ui-dist/oauth2-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 68 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded any time 7 | # it changes. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable/disable caching. By default caching is disabled. 18 | # Run rails dev:cache to toggle caching. 19 | if Rails.root.join("tmp", "caching-dev.txt").exist? 20 | config.action_controller.perform_caching = true 21 | config.action_controller.enable_fragment_cache_logging = true 22 | 23 | config.cache_store = :memory_store 24 | config.public_file_server.headers = { 25 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 26 | } 27 | else 28 | config.action_controller.perform_caching = false 29 | 30 | config.cache_store = :null_store 31 | end 32 | 33 | # Don't care if the mailer can't send. 34 | config.action_mailer.raise_delivery_errors = false 35 | 36 | config.action_mailer.perform_caching = false 37 | 38 | # Print deprecation notices to the Rails logger. 39 | config.active_support.deprecation = :log 40 | 41 | # Raise exceptions for disallowed deprecations. 42 | config.active_support.disallowed_deprecation = :raise 43 | 44 | # Tell Active Support which deprecation messages to disallow. 45 | config.active_support.disallowed_deprecation_warnings = [] 46 | 47 | # Debug mode disables concatenation and preprocessing of assets. 48 | # This option may cause significant delays in view rendering with a large 49 | # number of complex assets. 50 | config.assets.debug = true 51 | 52 | # Suppress logger output for asset requests. 53 | config.assets.quiet = true 54 | 55 | # Raises error for missing translations. 56 | # config.i18n.raise_on_missing_translations = true 57 | 58 | # Annotate rendered view with file names. 59 | # config.action_view.annotate_rendered_view_with_filenames = true 60 | 61 | # Use an evented file watcher to asynchronously detect changes in source code, 62 | # routes, locales, etc. This feature depends on the listen gem. 63 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 64 | 65 | # Uncomment if you wish to allow Action Cable access from any origin. 66 | # config.action_cable.disable_request_forgery_protection = true 67 | end 68 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/applications.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Applications 2 | 3 | ``` alert[info] 4 | This API endpoint requires a valid [user access token](/v2/docs/authentication). 5 | ``` 6 | 7 | ## Applications API 8 | 9 | An application provides a client ID and secret to interact with the Artsy API. See [Authentication](/v2/docs/authentication) for more information. 10 | 11 | #### Retrieving Applications 12 | 13 | Users can retrieve their own applications by following the [applications](#{ArtsyApi::V2.root}/applications) link from [root](#{ArtsyApi::V2.root}). 14 | 15 | ``` 16 | curl -v "#{ArtsyApi::V2.root}/applications?user_id=USER_ID" -H "X-Access-Token: ACCESS_TOKEN" 17 | ``` 18 | 19 | This endpoint accepts the following parameters. 20 | 21 | Name | Value | 22 | ----------:|:-------------------------------| 23 | user_id | Application owner (user) ID. | 24 | 25 | The response is a [paginated result](/v2/docs/pagination) with embedded applications. 26 | 27 | ``` json 28 | { 29 | "total_count" : 1, 30 | "_links" : { 31 | "self" : { 32 | "href" : "#{ArtsyApi::V2.root}/applications?user_id=..." 33 | }, 34 | "next" : { 35 | "href" : "#{ArtsyApi::V2.root}/applications?cursor=...&user_id=..." 36 | } 37 | }, 38 | "_embedded" : { 39 | "applications" : [ 40 | { 41 | "id":"...", 42 | ... 43 | } 44 | ] 45 | } 46 | } 47 | ``` 48 | 49 | A request without a "user_id" will be redirected accordingly with a 302 HTTP status code. 50 | 51 | #### Retrieving an Application 52 | 53 | Users can retrieve a specific application by ID by rendering the "application" link template from [root](#{ArtsyApi::V2.root}). 54 | 55 | ``` 56 | curl -v "#{ArtsyApi::V2.root}/applications/{id}" -H "X-Access-Token: ACCESS_TOKEN" 57 | ``` 58 | 59 | ``` alert[danger] 60 | Users are only authorized to retrieve their own applications. 61 | ``` 62 | 63 | ## Application JSON Format 64 | 65 | #{modelref://Application} 66 | 67 | #### Links 68 | 69 | Key | Target | 70 | ----------:|:--------------------------------| 71 | self | The application resource. | 72 | user | User that owns the application. | 73 | 74 | #### Example 75 | 76 | ``` json 77 | { 78 | "id" : "...", 79 | "created_at" : "2014-08-31T15:05:29+00:00", 80 | "updated_at" : "2014-08-31T15:05:29+00:00", 81 | "name" : "Test", 82 | "client_id" : "...", 83 | "client_secret" : "...", 84 | "enabled" : true, 85 | "_links" : { 86 | "self" : { 87 | "href" : "#{ArtsyApi::V2.root}/applications/..."}, 88 | "user" : { 89 | "href" : "#{ArtsyApi::V2.root}/users/4dc805b18101da0001000489" 90 | } 91 | } 92 | } 93 | ``` 94 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # While tests run files are not watched, reloading is not necessary. 12 | config.enable_reloading = false 13 | 14 | # Eager loading loads your entire application. When running a single test locally, 15 | # this is usually not necessary, and can slow down your test suite. However, it's 16 | # recommended that you enable it in continuous integration systems to ensure eager 17 | # loading is working properly before deploying your code. 18 | config.eager_load = ENV["CI"].present? 19 | 20 | # Configure public file server for tests with Cache-Control for performance. 21 | config.public_file_server.enabled = true 22 | config.public_file_server.headers = { 23 | "Cache-Control" => "public, max-age=#{1.hour.to_i}" 24 | } 25 | 26 | # Show full error reports and disable caching. 27 | config.consider_all_requests_local = true 28 | config.action_controller.perform_caching = false 29 | config.cache_store = :null_store 30 | 31 | # Render exception templates for rescuable exceptions and raise for other exceptions. 32 | config.action_dispatch.show_exceptions = :rescuable 33 | 34 | # Disable request forgery protection in test environment. 35 | config.action_controller.allow_forgery_protection = false 36 | 37 | config.action_mailer.perform_caching = false 38 | 39 | # Tell Action Mailer not to deliver emails to the real world. 40 | # The :test delivery method accumulates sent emails in the 41 | # ActionMailer::Base.deliveries array. 42 | config.action_mailer.delivery_method = :test 43 | 44 | # Print deprecation notices to the stderr. 45 | config.active_support.deprecation = :stderr 46 | 47 | # Raise exceptions for disallowed deprecations. 48 | config.active_support.disallowed_deprecation = :raise 49 | 50 | # Tell Active Support which deprecation messages to disallow. 51 | config.active_support.disallowed_deprecation_warnings = [] 52 | 53 | # Raises error for missing translations. 54 | # config.i18n.raise_on_missing_translations = true 55 | 56 | # Annotate rendered view with file names. 57 | # config.action_view.annotate_rendered_view_with_filenames = true 58 | 59 | # Raise error when a before_action's only/except options reference missing actions 60 | config.action_controller.raise_on_missing_callback_actions = true 61 | end 62 | -------------------------------------------------------------------------------- /app/controllers/webhook_deliveries_controller.rb: -------------------------------------------------------------------------------- 1 | class WebhookDeliveriesController < ApplicationController 2 | include Paginatable 3 | before_action :set_client_application_id, :set_access_token 4 | 5 | def index 6 | cache_key = "webhook_deliveries/#{@client_application_id}/page/#{@page}/size/#{@size}" 7 | 8 | # Use Rails.cache to cache the API response across requests 9 | response = Rails.cache.fetch(cache_key, expires_in: 10.minutes) do 10 | fetch_webhook_deliveries 11 | end 12 | 13 | @webhook_deliveries = response[:body].map do |webhook_delivery_data| 14 | build_webhook_delivery(webhook_delivery_data) 15 | end 16 | 17 | @total_pages = calculate_total_pages(response[:headers]["X-Total-Count"].to_i, @size) 18 | @current_page = @page.to_i 19 | rescue => e 20 | @error = e.message 21 | end 22 | 23 | def show 24 | response = ClientApplicationService.fetch_webhook_delivery( 25 | @access_token, 26 | id: params[:id] 27 | ) 28 | 29 | @webhook_delivery = build_webhook_delivery(response[:body]) 30 | rescue => e 31 | @error = e.message 32 | end 33 | 34 | def redeliver 35 | response = ClientApplicationService.redeliver_webhook_delivery( 36 | @access_token, 37 | id: params[:id] 38 | ) 39 | 40 | if response[:body][:status] == "success" 41 | flash[:notice] = "Webhook redelivery initiated successfully." 42 | else 43 | flash[:alert] = "Failed to redeliver the webhook event: #{response[:error]}" 44 | end 45 | 46 | redirect_back(fallback_location: client_application_webhook_delivery_path(params[:client_application_id], params[:id])) 47 | end 48 | 49 | private 50 | 51 | def build_webhook_delivery(data) 52 | WebhookDelivery.new( 53 | id: data[:id], 54 | response_status: data[:response_status], 55 | error_class: data[:error_class], 56 | created_at: data[:created_at], 57 | completed_at: data[:completed_at], 58 | webhook_event: data[:webhook_event], 59 | webhook_id: data[:webhook_id], 60 | webhook_url: data[:webhook_url] 61 | ) 62 | end 63 | 64 | def fetch_webhook_deliveries 65 | response = ClientApplicationService.fetch_webhook_deliveries( 66 | @access_token, 67 | client_application_id: @client_application_id, 68 | page: @page, 69 | size: @size, 70 | total_count: true 71 | ) 72 | 73 | {body: response[:body], headers: response[:headers]} 74 | end 75 | 76 | def set_client_application_id 77 | @client_application_id = params[:client_application_id] 78 | end 79 | 80 | def set_access_token 81 | @access_token = session[:access_token] 82 | end 83 | 84 | def client_application_partner_params 85 | params.require(:client_application).permit(:client_application_id) 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /app/controllers/pages_controller.rb: -------------------------------------------------------------------------------- 1 | class PagesController < ApplicationController 2 | include MarkdownHelper 3 | include CacheHelper 4 | 5 | skip_before_action :require_artsy_authentication 6 | 7 | def show 8 | raise "Invalid Id" unless params[:id] =~ %r{^[\w/]*$} 9 | 10 | filename = Rails.root.join("app/views/content/#{params[:id]}.md") 11 | @content = Rails.cache.fetch "content/#{params[:id]}/#{File.mtime(filename)}/#{session[:user_id]}" do 12 | text = File.read(filename) 13 | text = text.gsub(%r{\#\{(? [\w.,_/:=+\s\d-]*)\}}x) { 14 | var = $LAST_MATCH_INFO[:var] 15 | if var == "ArtsyApi::V2.root" 16 | ArtsyApi::V2.root 17 | elsif var == "ArtsyApi::V2.url" 18 | ArtsyApi::V2.url 19 | elsif var == "ArtsyApi::V2.docs_url" 20 | ArtsyApi::V2.docs_url 21 | elsif var == "ArtsyApi::V1.root" 22 | ArtsyApi::V1.root 23 | elsif var == "ArtsyApi::V1.url" 24 | ArtsyApi::V1.url 25 | elsif var == "ArtsyApi::V1.docs_url" 26 | ArtsyApi::V1.docs_url 27 | elsif var == "application_id" 28 | application_id 29 | elsif var.start_with? "resource://" 30 | resource var 31 | elsif var.start_with? "modelref://" 32 | modelref var 33 | else 34 | "unknown: #{var}" 35 | end 36 | } 37 | render_markdown text 38 | end 39 | end 40 | 41 | def start 42 | if authenticated? 43 | @client_applications = artsy_client.applications 44 | @selected_client_application = @client_applications.find { |app| app.id == params[:id] } if params.key?(:id) 45 | @selected_client_application ||= @client_applications.first if @client_applications.count == 1 46 | end 47 | end 48 | 49 | private 50 | 51 | def resource(var) 52 | parts = var.split("/")[2..] 53 | method = parts[0] 54 | args = parts[1..].map { |part| part.split("=", 2) }.to_h 55 | JSON.pretty_generate artsy_client.send(method, args)._get._response.body 56 | rescue => e 57 | Rails.logger.error e 58 | Rails.logger.error e.backtrace.join("\n") 59 | "error: #{e.message}" 60 | end 61 | 62 | def modelref(var) 63 | parts = var.split("/")[2..] 64 | model = parts[0] 65 | rc = <<-TEXT 66 | Key | Description | 67 | ---:|:------------| 68 | TEXT 69 | body = ArtsyApi::V2.client.connection.get("#{ArtsyApi::V2.root}/v2/docs/docs").body 70 | properties = body["models"] 71 | raise "missing models" unless properties 72 | 73 | properties = properties[model] 74 | raise "missing #{model}" unless properties 75 | 76 | properties = properties["properties"] 77 | raise "missing properties" unless properties 78 | 79 | properties.each_pair do |key, desc| 80 | next unless desc["description"] 81 | 82 | rc += "#{key.gsub("_", '\_')} | #{desc["description"]}\n" 83 | end 84 | rc 85 | rescue => e 86 | Rails.logger.error e 87 | Rails.logger.error e.backtrace.join("\n") 88 | "error: #{e.message}" 89 | end 90 | 91 | def application_id 92 | if authenticated? 93 | artsy_client.applications 94 | .try(:first) 95 | .try(:attributes) 96 | .try(:id) || "..." 97 | else 98 | "..." 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/pagination.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > General > Pagination and Sets 2 | 3 | ## Pagination And Sets 4 | 5 | Paginated results, including [artists](/v2/docs/artists) or [artworks](/v2/docs/artworks), consist of a set of pagination-related fields, such as counts, links between pages and embedded results. 6 | 7 | Each response contains the following fields. 8 | 9 | Key | Target | 10 | -----------:|:------------------------------------------------| 11 | total_count | Total count of items. | 12 | 13 | #### Total Count 14 | 15 | Counting objects is not free. The value of "total\_count" is never set by default. Specify "total\_count=1" in the query string to retrieve it. 16 | 17 | ``` 18 | curl -v "#{ArtsyApi::V2.root}/artworks?total_count=1" -H "X-XAPP-Token: XAPP_TOKEN" 19 | ``` 20 | 21 | ``` alert[warning] 22 | The value of total\_count should not be used as an exact expectation of how many results the API may return. It's possible that new items have been inserted or existing items removed since the moment this value has been retrieved. 23 | ``` 24 | 25 | #### Links 26 | 27 | Key | Target | 28 | ----------:|:------------------------------------------------------------| 29 | self | Current page. | 30 | next | Next page. If this value is not set, this is the last page. | 31 | 32 | #### Parameters 33 | 34 | All paginated APIs accept the following parameters. 35 | 36 | Key | Target | 37 | -----------:|:-----------------------------------------------------------------------------------------| 38 | total_count | Specify "total\_count=1" to retrieve the total count, see above. | 39 | size | Limit the number of embedded items in the response to this number. | 40 | cursor | A position within the results set. | 41 | offset | Skip this number of items, mutually exclusive with a cursor. | 42 | sample | Redirect to a random element in the collection. | 43 | 44 | Passing in a parameter of "sample" will redirect you to the canonical URL for a random element in the collection. It can be combo'ed with additional filter parameters, so one could query for a random upcoming show with: "#{ArtsyApi::V2.root}/shows?status=upcoming&sample=1" 45 | 46 | ``` alert[warning] 47 | The cursor and offset pagination parameters are mutually exclusive. Attempting to use both of them in an API call will result in an explicit 400 error from the API. We prefer cursor-based pagination, however there is a need for a more conventional based paging scheme. Passing in an offset parameter will first skip that number of records, and then begin cursor-based paging from there. 48 | ``` 49 | 50 | #### Example 51 | 52 | ``` json 53 | { 54 | "total_count" : 1, 55 | "_links" : { 56 | "self" : { 57 | "href" : "#{ArtsyApi::V2.root}/artists" 58 | }, 59 | "next" : { 60 | "href" : "#{ArtsyApi::V2.root}/artists?cursor=..." 61 | } 62 | }, 63 | "_embedded" : { 64 | "artists" : [ 65 | { 66 | "id" : "...", 67 | ... 68 | } 69 | ] 70 | } 71 | } 72 | ``` 73 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/collections.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Collections 2 | 3 | ## Collections API 4 | 5 | A collection belongs to a [user](/v2/docs/users) and contains [artworks](/v2/docs/artworks). A collection may be public or private: public collections are available for everyone to see and private collections are not. Every user has a default collection, often referred to as "Favorites" and called "Saved Artwork" by default. 6 | 7 | #### Retrieving Collections 8 | 9 | You can retrieve a user's public collections by following the "public\_collections" link from a [user](/v2/docs/users). Users can retrieve their own public and private collections by following the [collections](#{ArtsyApi::V2.root}/collections) link from [user details](/v2/docs/user_details). 10 | 11 | ``` 12 | curl -v "#{ArtsyApi::V2.root}/collections?user_id=USER_ID&private=true" -H "X-Access-Token: ACCESS_TOKEN" 13 | ``` 14 | 15 | This endpoint accepts the following parameters. 16 | 17 | Name | Value | 18 | ----------:|:--------------------------------------| 19 | user_id | Collection owner (user) ID, required. | 20 | private | Also retrieve private collections. | 21 | 22 | The response is a [paginated result](/v2/docs/pagination) with embedded collections. 23 | 24 | #### Retrieving a Collection 25 | 26 | Users can retrieve a specific collection by ID by rendering the "collection" link template from [root](#{ArtsyApi::V2.root}). 27 | 28 | ``` 29 | curl -v "#{ArtsyApi::V2.root}/collections/{id}?user_id=USER_ID" -H "X-Access-Token: ACCESS_TOKEN" 30 | ``` 31 | 32 | ``` alert[danger] 33 | Users are only authorized to retrieve their own private collections. 34 | ``` 35 | 36 | #### Creating and Updating Collections 37 | 38 | You can create a collection with POST to "collections" and update a collection with PUT on "collection". All fields from the collection JSON format below are supported, except "id". A "user\_id" is required. 39 | 40 | See [this topic](/v2/docs/collection_items) for documentation on how to add artworks to a collection. 41 | 42 | ## Collection Item JSON Format 43 | 44 | #{modelref://Collection} 45 | 46 | #### Links 47 | 48 | Key | Target | 49 | -----------------:|:-------------------------------------------------------------------| 50 | self | The collection resource. | 51 | user | [User](/v2/docs/users) that owns the collection. | 52 | artworks | [Artworks](/v2/docs/artworks) in this collection, sorted by position. | 53 | collection\_items | [Collection Items](/v2/docs/collection_items) in this collection. | 54 | 55 | #### Example 56 | 57 | ``` json 58 | { 59 | "id" : "...", 60 | "created_at" : "2014-08-31T15:05:29+00:00", 61 | "updated_at" : "2014-08-31T15:05:29+00:00", 62 | "name" : "Saved Artwork", 63 | "description" : "Default collection, favorites.", 64 | "default" : true, 65 | "private" : true, 66 | "_links" : { 67 | "self" : { 68 | "href" : "#{ArtsyApi::V2.root}/collections/..."}, 69 | "user" : { 70 | "href" : "#{ArtsyApi::V2.root}/users/4dc805b18101da0001000489" 71 | }, 72 | "artworks" : { 73 | "href" : "#{ArtsyApi::V2.root}/artworks?collection_id=...&user_id=..." 74 | }, 75 | "collection_items" : { 76 | "href" : "#{ArtsyApi::V2.root}/collection_items?collection_id=...&user_id=..." 77 | } 78 | } 79 | } 80 | ``` 81 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/artworks.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Artworks 2 | 3 | ## Artworks API 4 | 5 | An artwork is an artistic production created by an [artist](/v2/docs/artists), multiple artists or an artist collective. An artwork may be part of a series and be reproduced in prints. Artworks are available from Artsy [partners](/v2/docs/partners) and may be included in [shows](/v2/docs/shows). 6 | 7 | #### Retrieving Artworks 8 | 9 | Retrieve artworks by following the [artworks](#{ArtsyApi::V2.root}/artworks) link from [root](#{ArtsyApi::V2.root}). 10 | 11 | ``` 12 | curl -v "#{ArtsyApi::V2.root}/artworks" -H "X-XAPP-Token: XAPP_TOKEN" 13 | ``` 14 | 15 | This endpoint accepts the following parameters. 16 | 17 | Name | Value | 18 | -----------------:|:---------------------------------------------------------------------------------------------------| 19 | artist\_id | Retrieve artworks by a given [artist](/v2/docs/artists). | 20 | partner\_id | Retrieve artworks that belong to a given [partner](/v2/docs/partners). | 21 | show\_id | Retrieve artworks that belong to a given [show](/v2/docs/shows). | 22 | collection\_id | Retrieve artworks that belong to a given [collection](/v2/docs/collections), sorted by position. | 23 | user\_id | The user that owns the [collection](/v2/docs/collections), required when collection\_id is specified. | 24 | 25 | The response is a [paginated result](/v2/docs/pagination) with embedded artworks. 26 | 27 | #### Retrieving an Artwork 28 | 29 | Users can retrieve a specific artwork by ID by rendering the "artwork" link template from [root](#{ArtsyApi::V2.root}). 30 | 31 | ``` 32 | curl -v "#{ArtsyApi::V2.root}/artworks/{id}" -H "X-XAPP-Token: XAPP_TOKEN" 33 | ``` 34 | 35 | #### Retrieving Similar Artworks 36 | 37 | Artsy continuously computes a K-nearest-neighbor graph for artworks using data from the [Art Genome Project](https://artsy.net/about/the-art-genome-project). Retrieve artworks similar to another artwork by following the "similar\_artworks" link in an artwork resource, which calls this endpoint with the `similar\_to\_artwork_id` parameter. The response is a non-paginated set of similar artworks. 38 | 39 | ## Artwork JSON Format 40 | 41 | #{modelref://Artwork} 42 | 43 | #### Links 44 | 45 | Key | Target | 46 | ------------------:|:-------------------------------------------------| 47 | self | The artwork resource. | 48 | thumbnail | Default image thumbnail. | 49 | image | Curied image location. | 50 | permalink | An external location on the artsy.net website. | 51 | partner | [Partner](/v2/docs/partners) that owns the artwork. | 52 | artists | Artwork's [Artists](/v2/docs/artists). | 53 | genes | Artwork's [Genes](/v2/docs/genes). | 54 | similar\_artworks | Artwork similar to the artwork. | 55 | 56 | #### Embedded Collections 57 | 58 | Key | Target | 59 | -------------:|:--------------------------------------------------------------------------------| 60 | editions | A set of artwork [editions](/v2/docs/editions) available for this artwork. | 61 | 62 | #### Example 63 | 64 | ``` json 65 | #{resource://artwork/id=516dfb9ab31e2b2270000c45} 66 | ``` 67 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/collection_items.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Collection Items 2 | 3 | ## Collection Items API 4 | 5 | A collection item belongs to a [collection](/v2/docs/collection), a [user](/v2/docs/users) and currently references an [artwork](/v2/docs/artworks). 6 | 7 | #### Retrieving Collection Items 8 | 9 | ``` alert[info] 10 | If you're just trying to retrieve artworks that belong to a collection, use the [artworks](/v2/docs/artworks) API. 11 | ``` 12 | 13 | You can retrieve collection items by following the "collection\_items" link from a [collection](/v2/docs/collections). 14 | 15 | ``` 16 | curl -v "#{ArtsyApi::V2.root}/collection_items?collection_id=...&user_id=USER_ID" -H "X-Access-Token: ACCESS_TOKEN" 17 | ``` 18 | 19 | This endpoint accepts the following parameters. 20 | 21 | Name | Value | 22 | ----------------:|:--------------------------------------| 23 | collection_id | Collection ID, required. | 24 | user_id | Collection owner (user) ID, required. | 25 | 26 | The response is a [paginated result](/v2/docs/pagination) with embedded collection items. 27 | 28 | #### Retrieving a Collection Item 29 | 30 | Users can retrieve a specific collection item by ID by rendering the "collection\_item" link template from [root](#{ArtsyApi::V2.root}). 31 | 32 | ``` 33 | curl -v "#{ArtsyApi::V2.root}/collection_items/{id}?collection_id=...&user_id=USER_ID" -H "X-Access-Token: ACCESS_TOKEN" 34 | ``` 35 | 36 | #### Creating and Updating Collection Items 37 | 38 | You can create collection items with POST to "collection\_items" and update a collection item, typically to change its position within a collection, with PUT to "collection\_item". 39 | 40 | The following Ruby example creates a collection and adds an artwork to it. 41 | 42 | ```ruby 43 | require 'hyperclient' 44 | 45 | # access token should be obtained via OAuth 46 | api = Hyperclient.new('#{ArtsyApi::V2.root}') do |api| 47 | api.headers['Accept'] = 'application/vnd.artsy-v2+json' 48 | api.headers['X-Access-Token'] = '...' 49 | end 50 | 51 | # retrieve current user 52 | current_user = api.current_user 53 | 54 | # fetch the user's default collection 55 | collection = api.collections(user_id: current_user.id, default: true, private: true).first 56 | 57 | # add an artwork to the collection 58 | artwork = api.artworks.first 59 | collection.collection_items._post(artwork_id: artwork.id) 60 | ``` 61 | 62 | ## Collection Item JSON Format 63 | 64 | #{modelref://CollectionItem} 65 | 66 | #### Links 67 | 68 | Key | Target | 69 | -----------------:|:-------------------------------------------------------------------| 70 | self | The collection resource. | 71 | user | [User](/v2/docs/users) that owns the collection. | 72 | collection | [Collection](/v2/docs/collections) that this item belongs to. | 73 | artwork | [Artwork](/v2/docs/artworks) referenced by this item. | 74 | 75 | #### Example 76 | 77 | ``` json 78 | { 79 | "id" : "...", 80 | "created_at" : "2014-11-25T19:30:15+00:00", 81 | "updated_at" : "2014-11-25T19:30:15+00:00", 82 | "position" : 1, 83 | "_links" : { 84 | "self" : { 85 | "href" : "#{ArtsyApi::V2.root}/collection_items/...?collection_id=...&user_id=..." 86 | }, 87 | "user" : { 88 | "href" : "#{ArtsyApi::V2.root}/users/..." 89 | }, 90 | "collection" : { 91 | "href" : "#{ArtsyApi::V2.root}/collections/..." 92 | }, 93 | "artwork" : { 94 | "href" : "#{ArtsyApi::V2.root}/artworks/..." 95 | } 96 | } 97 | } 98 | ``` 99 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/search.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Search 2 | 3 | ## Search API 4 | 5 | Search for anything on Artsy. 6 | 7 | #### Retrieving Search Results 8 | 9 | Search for anything by following the [search](#{ArtsyApi::V2.root}/search) link from [root](#{ArtsyApi::V2.root}) with a query parameter. 10 | 11 | ``` 12 | curl -v "#{ArtsyApi::V2.root}/search?q=Andy+Warhol" -H "X-XAPP-Token: XAPP_TOKEN" 13 | ``` 14 | 15 | The response is a [paginated result](/v2/docs/pagination) with embedded search results. 16 | 17 | ## Search Result JSON Format 18 | 19 | #{modelref://SearchResult} 20 | 21 | #### Possible Types 22 | 23 | Type | Resource | 24 | -------------:|:---------------------------------------------------| 25 | Artist | An Artsy [artist](/v2/docs/artists). | 26 | Artwork | An Artsy [artwork](/v2/docs/artworks). | 27 | Profile | An Artsy [profile](/v2/docs/profiles). | 28 | Gene | An Artsy [gene](/v2/docs/genes). | 29 | Show | An Artsy [show](/v2/docs/shows). | 30 | 31 | The API will also return search results with a "null" value for "type". Those are not supported by the API (yet) and may include features, posts or shows. 32 | 33 | #### Links 34 | 35 | Key | Target | 36 | ----------:|:------------------------------------------------| 37 | self | The resource found, when available. | 38 | thumbnail | Image thumbnail, when available. | 39 | permalink | An external location on the artsy.net website. | 40 | 41 | ``` alert[warning] 42 | You can follow the "self" link on each individual search result, however because of delays in indexing and content restrictions there's no guarantee that those links will always return a resource. Notably, only a subset of artworks is available via the API, whereas search returns all content published on artsy.net. 43 | ``` 44 | 45 | #### Advanced Search 46 | 47 | You can leverage the filtering capabilities of [Google Custom Search](https://developers.google.com/custom-search/v2/docs/structured_search) that provides the back-end for the Artsy search API. 48 | 49 | For example, only search for artists by specifying an "artist" open-graph type with `more:pagemap:metatags-og_type:artist`. 50 | 51 | ``` 52 | curl -v "#{ArtsyApi::V2.root}/search?q=Andy+Warhol+more:pagemap:metatags-og_type:artist" -H "X-XAPP-Token: XAPP_TOKEN" 53 | ``` 54 | 55 | #### Spell Check 56 | 57 | If the API fails to find any results for a term it will attempt to correct its spelling. The response will be a 302 redirect to the suggested search URL. For example, searching for "Tauba Orbach" will redirect to a search for "Tauba Auerbach". 58 | 59 | ``` 60 | curl -v "#{ArtsyApi::V2.root}/search?q=Tauba+Orbach" -H "X-XAPP-Token: XAPP_TOKEN" 61 | 62 | < HTTP/1.1 302 Found 63 | < Content-Type: application/json 64 | < Location: #{ArtsyApi::V2.root}/api/search?q=Tauba+Auerbach 65 | ``` 66 | 67 | ``` json 68 | { 69 | "_links" : { 70 | "location" : { 71 | "href" : "#{ArtsyApi::V2.root}/search?q=Tauba+Auerbach" 72 | } 73 | } 74 | } 75 | ``` 76 | 77 | #### Public Artworks, Stale and New Content 78 | 79 | Search indexing is not immediate. Search results may not always contain content that has been very recently added. 80 | 81 | Search results may contain metadata about artworks that are not publicly accessible via the Artsy API or that describe content that has been removed since it was last indexed. Following links for such results may result in a 404 Not Found error. 82 | 83 | #### Example 84 | 85 | ``` json 86 | #{resource://search/q=Andy Warhol} 87 | ``` 88 | -------------------------------------------------------------------------------- /config/environments/staging.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 18 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 19 | # config.require_master_key = true 20 | 21 | # Disable serving static files from the `/public` folder by default since 22 | # Apache or NGINX already handles this. 23 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 33 | 34 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 35 | # config.action_controller.asset_host = 'http://assets.example.com' 36 | 37 | # Specifies the header that your server uses for sending files. 38 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 40 | 41 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 42 | config.force_ssl = true 43 | 44 | # Use the lowest log level to ensure availability of diagnostic information 45 | # when problems arise. 46 | config.log_level = :info 47 | 48 | # Prepend all log lines with the following tags. 49 | config.log_tags = [:request_id] 50 | 51 | # Use a different cache store in production. 52 | # config.cache_store = :mem_cache_store 53 | 54 | # Use a real queuing backend for Active Job (and separate queues per environment) 55 | # config.active_job.queue_adapter = :resque 56 | # config.active_job.queue_name_prefix = "doppler_#{Rails.env}" 57 | 58 | config.action_mailer.perform_caching = false 59 | 60 | # Ignore bad email addresses and do not raise email delivery errors. 61 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 62 | # config.action_mailer.raise_delivery_errors = false 63 | 64 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 65 | # the I18n.default_locale when a translation cannot be found). 66 | config.i18n.fallbacks = true 67 | 68 | # Send deprecation notices to registered listeners. 69 | config.active_support.deprecation = :notify 70 | 71 | # Use default logging formatter so that PID and timestamp are not suppressed. 72 | config.log_formatter = ::Logger::Formatter.new 73 | 74 | # Use a different logger for distributed setups. 75 | # require 'syslog/logger' 76 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 77 | 78 | if ENV["RAILS_LOG_TO_STDOUT"].present? 79 | logger = ActiveSupport::Logger.new($stdout) 80 | logger.formatter = config.log_formatter 81 | config.logger = ActiveSupport::TaggedLogging.new(logger) 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/artists.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > Resources > Artists 2 | 3 | ## Artists API 4 | 5 | An artist creates [artworks](/v2/docs/artworks). 6 | 7 | An artist is generally one person, but can also be two people collaborating, a collective of people, or even a mysterious entity such as "Banksy". 8 | 9 | #### Retrieving Artists 10 | 11 | Retrieve artists by following the [artists](#{ArtsyApi::V2.root}/artists) link from [root](#{ArtsyApi::V2.root}). 12 | 13 | ``` 14 | curl -v "#{ArtsyApi::V2.root}/artists" -H "X-XAPP-Token: XAPP_TOKEN" 15 | ``` 16 | 17 | This endpoint accepts the following parameters. 18 | 19 | Name | Value | 20 | -----------------------:|:--------------------------------------------------------------------------------------------| 21 | artwork\_id | Retrieve artists for a given [artwork](/v2/docs/artworks). | 22 | similar\_to\_artist\_id | Return artists similar to a given artist. | 23 | similarity\_type | Similarity type, either `default` or `contemporary`. See below. | 24 | gene\_id | Return a set of artists that represent a given [gene](/v2/docs/genes). | 25 | artworks | Only return artists with artworks. | 26 | published\_artworks | Only return artists with published artworks. | 27 | partner\_id | Return artists with artworks that belong to the [partner](/v2/docs/partners). | 28 | 29 | The response is a [paginated result](/v2/docs/pagination) with embedded artists. 30 | 31 | #### Retrieving an Artist 32 | 33 | Users can retrieve a specific artist by ID by rendering the "artist" link template from [root](#{ArtsyApi::V2.root}). 34 | 35 | ``` 36 | curl -v "#{ArtsyApi::V2.root}/artists/{id}" -H "X-XAPP-Token: XAPP_TOKEN" 37 | ``` 38 | 39 | #### Retrieving an Artwork's Artists 40 | 41 | Follow the "artists" link from an [artwork](/v2/docs/artworks), which calls this endpoint with the `artwork_id` parameter. 42 | 43 | #### Retrieving Similar Artists 44 | 45 | Artsy continuously computes a K-nearest-neighbor graph for artists using data from the [Art Genome Project](https://artsy.net/about/the-art-genome-project). Retrieve artists similar to another artist by following the "similar\_artists" or the "similar\_contemporary\_artists" links in an artist resource, which calls this endpoint with the `similar_to_artist_id` parameter and an optional `similarity_type` value. The response is a non-paginated set of similar artists or similar contemporary artists, respectively. 46 | 47 | ## Artist JSON Format 48 | 49 | #{modelref://Artist} 50 | 51 | #### Links 52 | 53 | Key | Target | 54 | --------------------------------:|:------------------------------------------------| 55 | self | The artist resource. | 56 | thumbnail | Default image thumbnail. | 57 | image | Curied image location. | 58 | permalink | An external location on the artsy.net website. | 59 | artworks | All artist's [artworks](/v2/docs/artworks). | 60 | similar\_artists | Artists similar to this artist. | 61 | similar\_contemporary\_artists | Contemporary artists similar to this artist. | 62 | 63 | #### Example 64 | 65 | ``` json 66 | #{resource://artist/id=4d8b92b34eb68a1b2c0003f4} 67 | ``` 68 | -------------------------------------------------------------------------------- /hokusai/staging.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: doppler-web 5 | namespace: default 6 | labels: 7 | app: doppler 8 | component: web 9 | layer: application 10 | app.kubernetes.io/version: staging 11 | spec: 12 | replicas: 1 13 | strategy: 14 | rollingUpdate: 15 | maxSurge: 1 16 | maxUnavailable: 0 17 | type: RollingUpdate 18 | selector: 19 | matchLabels: 20 | app: doppler 21 | component: web 22 | layer: application 23 | template: 24 | metadata: 25 | labels: 26 | app: doppler 27 | component: web 28 | layer: application 29 | app.kubernetes.io/version: staging 30 | name: doppler-web 31 | spec: 32 | initContainers: 33 | - name: setenv 34 | image: 585031190124.dkr.ecr.us-east-1.amazonaws.com/fortress:staging 35 | imagePullPolicy: Always 36 | args: 37 | - python 38 | - src/load/load.py 39 | - kubernetes 40 | - staging 41 | - doppler 42 | envFrom: 43 | - configMapRef: 44 | name: secrets-config 45 | volumeMounts: 46 | - name: secrets 47 | mountPath: /secrets 48 | containers: 49 | - name: doppler-web 50 | env: 51 | - name: TRACE_AGENT_HOSTNAME 52 | valueFrom: 53 | fieldRef: 54 | fieldPath: spec.nodeName 55 | - name: DD_VERSION 56 | valueFrom: 57 | fieldRef: 58 | fieldPath: metadata.labels['app.kubernetes.io/version'] 59 | envFrom: 60 | - configMapRef: 61 | name: secrets-config 62 | - configMapRef: 63 | name: doppler-environment 64 | volumeMounts: 65 | - name: secrets 66 | mountPath: /secrets 67 | readOnly: true 68 | image: 585031190124.dkr.ecr.us-east-1.amazonaws.com/doppler:staging 69 | imagePullPolicy: Always 70 | ports: 71 | - name: doppler-http 72 | containerPort: 8080 73 | resources: 74 | requests: 75 | cpu: 100m 76 | memory: 256Mi 77 | limits: 78 | memory: 500Mi 79 | readinessProbe: 80 | httpGet: 81 | port: doppler-http 82 | path: /health/ping 83 | httpHeaders: 84 | - name: X-Forwarded-Proto 85 | value: https 86 | initialDelaySeconds: 10 87 | periodSeconds: 10 88 | timeoutSeconds: 5 89 | dnsPolicy: ClusterFirst 90 | dnsConfig: 91 | options: 92 | - name: ndots 93 | value: '1' 94 | serviceAccountName: doppler 95 | affinity: 96 | nodeAffinity: 97 | requiredDuringSchedulingIgnoredDuringExecution: 98 | nodeSelectorTerms: 99 | - matchExpressions: 100 | - key: tier 101 | operator: In 102 | values: 103 | - foreground 104 | volumes: 105 | - name: secrets 106 | emptyDir: {} 107 | --- 108 | apiVersion: v1 109 | kind: Service 110 | metadata: 111 | labels: 112 | app: doppler 113 | component: web 114 | layer: application 115 | name: doppler-web-internal 116 | namespace: default 117 | spec: 118 | ports: 119 | - port: 8080 120 | protocol: TCP 121 | name: http 122 | targetPort: 8080 123 | selector: 124 | app: doppler 125 | layer: application 126 | component: web 127 | type: ClusterIP 128 | --- 129 | apiVersion: networking.k8s.io/v1 130 | kind: Ingress 131 | metadata: 132 | name: doppler-2025 133 | annotations: 134 | nginx.ingress.kubernetes.io/whitelist-source-range: {{ cloudflareIpSourceRanges|join(',') }} 135 | spec: 136 | ingressClassName: external-nginx 137 | rules: 138 | - host: developers-staging.artsy.net 139 | http: 140 | paths: 141 | - path: / 142 | pathType: Prefix 143 | backend: 144 | service: 145 | name: doppler-web-internal 146 | port: 147 | name: http 148 | -------------------------------------------------------------------------------- /hokusai/production.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: doppler-web 5 | namespace: default 6 | labels: 7 | app: doppler 8 | component: web 9 | layer: application 10 | app.kubernetes.io/version: production 11 | spec: 12 | replicas: 2 13 | strategy: 14 | rollingUpdate: 15 | maxSurge: 0 16 | maxUnavailable: 1 17 | type: RollingUpdate 18 | selector: 19 | matchLabels: 20 | app: doppler 21 | component: web 22 | layer: application 23 | template: 24 | metadata: 25 | labels: 26 | app: doppler 27 | component: web 28 | layer: application 29 | app.kubernetes.io/version: production 30 | name: doppler-web 31 | spec: 32 | initContainers: 33 | - name: setenv 34 | image: 585031190124.dkr.ecr.us-east-1.amazonaws.com/fortress:production 35 | imagePullPolicy: Always 36 | args: 37 | - python 38 | - src/load/load.py 39 | - kubernetes 40 | - production 41 | - doppler 42 | envFrom: 43 | - configMapRef: 44 | name: secrets-config 45 | volumeMounts: 46 | - name: secrets 47 | mountPath: /secrets 48 | containers: 49 | - name: doppler-web 50 | env: 51 | - name: TRACE_AGENT_HOSTNAME 52 | valueFrom: 53 | fieldRef: 54 | fieldPath: spec.nodeName 55 | - name: DD_VERSION 56 | valueFrom: 57 | fieldRef: 58 | fieldPath: metadata.labels['app.kubernetes.io/version'] 59 | envFrom: 60 | - configMapRef: 61 | name: secrets-config 62 | - configMapRef: 63 | name: doppler-environment 64 | volumeMounts: 65 | - name: secrets 66 | mountPath: /secrets 67 | readOnly: true 68 | image: 585031190124.dkr.ecr.us-east-1.amazonaws.com/doppler:production 69 | imagePullPolicy: Always 70 | ports: 71 | - name: doppler-http 72 | containerPort: 8080 73 | resources: 74 | requests: 75 | cpu: 200m 76 | memory: 800Mi 77 | limits: 78 | memory: 1Gi 79 | readinessProbe: 80 | httpGet: 81 | port: doppler-http 82 | path: /health/ping 83 | httpHeaders: 84 | - name: X-Forwarded-Proto 85 | value: https 86 | initialDelaySeconds: 10 87 | periodSeconds: 10 88 | timeoutSeconds: 5 89 | dnsPolicy: ClusterFirst 90 | dnsConfig: 91 | options: 92 | - name: ndots 93 | value: '1' 94 | serviceAccountName: doppler 95 | affinity: 96 | nodeAffinity: 97 | requiredDuringSchedulingIgnoredDuringExecution: 98 | nodeSelectorTerms: 99 | - matchExpressions: 100 | - key: tier 101 | operator: In 102 | values: 103 | - foreground 104 | volumes: 105 | - name: secrets 106 | emptyDir: {} 107 | --- 108 | apiVersion: v1 109 | kind: Service 110 | metadata: 111 | labels: 112 | app: doppler 113 | component: web 114 | layer: application 115 | name: doppler-web-internal 116 | namespace: default 117 | spec: 118 | ports: 119 | - port: 8080 120 | protocol: TCP 121 | name: http 122 | targetPort: 8080 123 | selector: 124 | app: doppler 125 | layer: application 126 | component: web 127 | type: ClusterIP 128 | --- 129 | apiVersion: networking.k8s.io/v1 130 | kind: Ingress 131 | metadata: 132 | name: doppler-2025 133 | annotations: 134 | nginx.ingress.kubernetes.io/whitelist-source-range: {{ cloudflareIpSourceRanges|join(',') }} 135 | spec: 136 | ingressClassName: external-nginx 137 | rules: 138 | - host: developers.artsy.net 139 | http: 140 | paths: 141 | - path: / 142 | pathType: Prefix 143 | backend: 144 | service: 145 | name: doppler-web-internal 146 | port: 147 | name: http 148 | -------------------------------------------------------------------------------- /vendor/assets/swagger-ui-dist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_args": [ 3 | [ 4 | { 5 | "raw": "swagger-ui-dist", 6 | "scope": null, 7 | "escapedName": "swagger-ui-dist", 8 | "name": "swagger-ui-dist", 9 | "rawSpec": "", 10 | "spec": "latest", 11 | "type": "tag" 12 | }, 13 | "/Users/dblock/source/artsy/gravity/dblock" 14 | ] 15 | ], 16 | "_from": "swagger-ui-dist@latest", 17 | "_hasShrinkwrap": false, 18 | "_id": "swagger-ui-dist@3.19.4", 19 | "_inCache": true, 20 | "_location": "/swagger-ui-dist", 21 | "_nodeVersion": "8.11.3", 22 | "_npmOperationalInternal": { 23 | "host": "s3://npm-registry-packages", 24 | "tmp": "tmp/swagger-ui-dist_3.19.4_1540010910667_0.15024764813727365" 25 | }, 26 | "_npmUser": { 27 | "name": "swagger-api", 28 | "email": "apiteam@swagger.io" 29 | }, 30 | "_npmVersion": "5.10.0", 31 | "_phantomChildren": {}, 32 | "_requested": { 33 | "raw": "swagger-ui-dist", 34 | "scope": null, 35 | "escapedName": "swagger-ui-dist", 36 | "name": "swagger-ui-dist", 37 | "rawSpec": "", 38 | "spec": "latest", 39 | "type": "tag" 40 | }, 41 | "_requiredBy": [ 42 | "#USER" 43 | ], 44 | "_resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.19.4.tgz", 45 | "_shasum": "18053b1e7fa135c00dce0f2ebfaeb05d186cd5b5", 46 | "_shrinkwrap": null, 47 | "_spec": "swagger-ui-dist", 48 | "_where": "/Users/dblock/source/artsy/gravity/dblock", 49 | "bugs": { 50 | "url": "https://github.com/swagger-api/swagger-ui/issues" 51 | }, 52 | "contributors": [ 53 | { 54 | "url": "in alphabetical order" 55 | }, 56 | { 57 | "name": "Anna Bodnia", 58 | "email": "anna.bodnia@gmail.com" 59 | }, 60 | { 61 | "name": "Buu Nguyen", 62 | "email": "buunguyen@gmail.com" 63 | }, 64 | { 65 | "name": "Josh Ponelat", 66 | "email": "jponelat@gmail.com" 67 | }, 68 | { 69 | "name": "Kyle Shockey", 70 | "email": "kyleshockey1@gmail.com" 71 | }, 72 | { 73 | "name": "Robert Barnwell", 74 | "email": "robert@robertismy.name" 75 | }, 76 | { 77 | "name": "Sahar Jafari", 78 | "email": "shr.jafari@gmail.com" 79 | } 80 | ], 81 | "dependencies": {}, 82 | "description": "[![NPM version](https://badge.fury.io/js/swagger-ui-dist.svg)](http://badge.fury.io/js/swagger-ui-dist)", 83 | "devDependencies": {}, 84 | "directories": {}, 85 | "dist": { 86 | "integrity": "sha512-YxC2mN53oXzF3hobm9cFhR14j9amNe2zUeKQa/tHckDJKItIxLQd+ksfeJgKw+pK04C2yppscOCFlIAvgR+jNQ==", 87 | "shasum": "18053b1e7fa135c00dce0f2ebfaeb05d186cd5b5", 88 | "tarball": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.19.4.tgz", 89 | "fileCount": 16, 90 | "unpackedSize": 9060256, 91 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbyrOfCRA9TVsSAnZWagAAZV0P/R5ozuKXhS+Pjd+CwxPz\n2ZTsjMDRR+2j92+SlJbrt0wmLe4TeQAerDWrou5Y64H4MZdjApaIuMySi/jn\nwPQD3pi8dAOV03nciXYquhivQSLMfMsCLHcrMogobqKbQk970KhrXCbXbQwh\noh/l5dJ6FLTwpk7HW3+X7oEPaNKGzdtDPB1B8u6WmZBanlE8s5ygBv1be0ps\n80UYUvDCP88VqNWyV7LUe7/CzjYMCI80CGfNE7TESZhsT2Xy/CutBlIu1tMC\nzeHnEk0hNX1rVIyMgEovTrqPXYl4NPZ7Ox68znU9m/v0PpAYqr4oF4CZfonM\nyUSS92gNitQ4niS7uXvkNWCZ2RUVVoefyFEWwJJhs/3X+gmGD2G710GGFar9\n9K/JF4oYG8vggEyIeu8TYr/sS1SWGfuf5N/kKTt6vBHM7EHf1FOzQD1UKXaN\nC9PemnlYsQ41Ml+fJIRs8h9phwXpPXkT4Ql2OofXXbFGaoilqFzN/R2jyZRt\nFpMK/MyibZOHtmbrkNfSXU39nyFec81nTxEAlBAuTWGIGCz+Iu7su3dl9pXm\nZilsWaWvv2xg41UjKlR+zWjpKKpciUBu7ESSdkqXeiFXJfwONZ6lzA/v88uv\nVva6N6VZ/iY9WouoYfpk2oDxBW8cudLlU7yJuMtl8J+fsorf1HnqCxR/3YeX\ne2Vl\r\n=dhm6\r\n-----END PGP SIGNATURE-----\r\n" 92 | }, 93 | "homepage": "https://github.com/swagger-api/swagger-ui#readme", 94 | "license": "Apache-2.0", 95 | "main": "index.js", 96 | "maintainers": [ 97 | { 98 | "name": "kyleshockey", 99 | "email": "kyle.shockey1@gmail.com" 100 | }, 101 | { 102 | "name": "swagger", 103 | "email": "apiteam@swagger.io" 104 | }, 105 | { 106 | "name": "swagger-api", 107 | "email": "apiteam@swagger.io" 108 | } 109 | ], 110 | "name": "swagger-ui-dist", 111 | "optionalDependencies": {}, 112 | "readme": "ERROR: No README data found!", 113 | "repository": { 114 | "type": "git", 115 | "url": "git+ssh://git@github.com/swagger-api/swagger-ui.git" 116 | }, 117 | "version": "3.19.4" 118 | } 119 | -------------------------------------------------------------------------------- /app/views/content/v2/docs/http.md: -------------------------------------------------------------------------------- 1 | [Public API Documentation](/v2) > General > Requests & Responses 2 | 3 | ## Requests & Responses 4 | 5 | ### Staging and Production 6 | 7 | The base URL of the API is [https://api.artsy.net/api](https://api.artsy.net/api). 8 | 9 | A test (staging) environment is also available at [https://stagingapi.artsy.net/api](https://stagingapi.artsy.net/api). 10 | 11 | ``` alert[info] 12 | Staging can only take moderate traffic and does not have 100% uptime. The environment is rebuilt nightly around 5:30AM EST with a large amount of production data. Everything you may have created in staging the day before, including applications, will likely be destroyed within 24 hours. Please also note that staging can't send e-mails. 13 | ``` 14 | 15 | ### Verbs 16 | 17 | The API supports "OPTIONS", "HEAD", "POST" and "PUT". 18 | 19 | * GET: Retrieves a resource or a collection of resources. 20 | * POST: Creates resources. 21 | * PUT: Updates a resource. 22 | * DELETE: Deletes a resource. 23 | 24 | ### POST and PUT Content Types 25 | 26 | When sending data via POST or PUT you may supply all the parameters in the query string or in the body of the request. The API accepts form data with `Content-Type: application/x-www-form-urlencoded` or JSON data with `Content-Type: application/json`. 27 | 28 | Requests with other content-types will fail with `406 Not Acceptable` and a JSON error response. 29 | 30 | ``` 31 | curl -X POST "https://stagingapi.artsy.net/api/tokens/xapp_token" -H "Content-Type:invalid/type" -d "x=y" 32 | ``` 33 | 34 | ``` 35 | HTTP/1.1 406 Not Acceptable 36 | Content-Type: application/json 37 | ``` 38 | 39 | ``` json 40 | { 41 | "error" : "The requested content-type 'invalid/type' is not supported." 42 | } 43 | ``` 44 | 45 | ### JSON Response Format 46 | 47 | #### JSON+HAL 48 | 49 | All response bodies are in the [JSON+HAL Hypermedia format](http://stateless.co/hal_specification.html). 50 | 51 | The simplest possible response is an empty JSON document. 52 | 53 | ``` json 54 | {} 55 | ``` 56 | 57 | A JSON document can have keys and values. Typical resources contain a unique "id". 58 | 59 | ``` json 60 | { 61 | "id" : "4d8b92b34eb68a1b2c0003f4", 62 | "name" : "Andy Warhol" 63 | } 64 | ``` 65 | 66 | #### Timestamps 67 | 68 | Most resources also contain a "created\_at" and "updated\_at" UTC timestamp in the [ISO 8601](http://en.wikipedia.org/wiki/ISO_8601) format, without millisecond precision. 69 | 70 | ``` json 71 | { 72 | "reated_at" : "2010-08-23T14:15:30+00:00", 73 | "updated_at" : "2014-08-29T14:25:57+00:00" 74 | } 75 | ``` 76 | 77 | #### Links 78 | 79 | Links are always contained directly within a resource under the "_links" key. Links have a relation (aka. 'rel'). This indicates the semantic, or the meaning, of a particular link. The example below is a link to an artist's artworks. 80 | 81 | ``` json 82 | { 83 | "_links" : { 84 | "artworks" : { 85 | "href" : "#{ArtsyApi::V2.root}/artworks?artist_id=4d8b92b34eb68a1b2c0003f4" 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | All top-level resources have a link to "self". 92 | 93 | ``` json 94 | { 95 | "_links" : { 96 | "self" : { 97 | "href" : "#{ArtsyApi::V2.root}/artists/4d8b92b34eb68a1b2c0003f4" 98 | } 99 | } 100 | } 101 | ``` 102 | 103 | For more advanced link structures see [links](/v2/docs/links). 104 | 105 | #### Collections 106 | 107 | Collections of resources are returned the "_embedded" key under a type sub-key. The following example is an empty collection of artworks. 108 | 109 | ``` json 110 | { 111 | "total_count" : 0, 112 | "_links" : { 113 | "self" : { 114 | "href" : "#{ArtsyApi::V2.root}/..." 115 | } 116 | }, 117 | "_embedded" : { 118 | "artworks": [ ] 119 | } 120 | } 121 | ``` 122 | 123 | For more information on iterating through large amounts of data or for obtaining counts see [pagination](/v2/docs/pagination). 124 | 125 | #### Errors 126 | 127 | HTTP status codes are used to indicate success or failure of a request. 128 | 129 | * 200 OK - Request succeeded. 130 | * 201 Created - Resource created. 131 | 132 | Error codes in the 400 range contain a JSON response that describes the error. 133 | 134 | ``` json 135 | { 136 | "type" : "other_error", 137 | "message" : "Artist Not Found" 138 | } 139 | ``` 140 | 141 | For more information about various error types see [errors](/v2/docs/errors). 142 | 143 | Error codes in the 500 range are not expected and should be [reported](/help). 144 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to Doppler 2 | ======================= 3 | 4 | Doppler is work of [many people](https://github.com/artsy/doppler/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/artsy/doppler/pulls), [propose features and discuss issues](https://github.com/artsy/doppler/issues). 5 | 6 | #### Fork the Project 7 | 8 | Fork the [project on Github](https://github.com/artsy/doppler) and check out your copy. 9 | 10 | ``` 11 | git clone https://github.com/contributor/doppler.git 12 | cd doppler 13 | git remote add upstream https://github.com/artsy/doppler.git 14 | ``` 15 | 16 | #### Create a Topic Branch 17 | 18 | Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. 19 | 20 | ``` 21 | git checkout main 22 | git pull upstream main 23 | git checkout -b my-feature-branch 24 | ``` 25 | 26 | #### Bundle Install and Test 27 | 28 | Ensure that you can build the project and run tests. 29 | 30 | _Note_: Modern Firefox support for selenium-webdriver is [troubled as of January 2017](https://github.com/teamcapybara/capybara/issues/1710). 31 | 32 | A compatible older version of Firefox is available [here](https://ftp.mozilla.org/pub/firefox/releases/45.7.0esr/). 33 | 34 | ``` 35 | bundle install 36 | bundle exec rake 37 | ``` 38 | 39 | #### Run Doppler 40 | 41 | You can run Doppler against an Artsy production environment, however we recommend playing with [staging](https://stagingapi.artsy.net). Please note that staging is rebuilt weekly and does not have 100% uptime. Staging has a copy of production data, so if you create a user or an application in production on artsy.net, it will be made available in staging in 7 days. Please also note that staging doesn't send e-mail, so some features may not work. 42 | 43 | To run Doppler you need a client ID and secret, which you can obtain from [developers-staging.artsy.net](https://developers-staging.artsy.net). Please note that staging is rebuilt weekly, so all your test data will be lost. 44 | 45 | Create a `.env` file. 46 | 47 | ``` 48 | ARTSY_API_URL=https://stagingapi.artsy.net 49 | ARTSY_API_CLIENT_ID=... 50 | ARTSY_API_CLIENT_SECRET=... 51 | JWT_SECRET=... 52 | ``` 53 | 54 | Run `foreman start`. 55 | 56 | Navigate to [http://localhost:5000](http://localhost:5000). 57 | 58 | #### Write Tests 59 | 60 | Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to [spec/features](spec/features). 61 | 62 | We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. 63 | 64 | #### Write Code 65 | 66 | Implement your feature or bug fix. 67 | 68 | Ruby style is enforced with [Rubocop](https://github.com/bbatsov/rubocop), run `bundle exec rubocop` and fix any style issues highlighted. 69 | 70 | Make sure that `bundle exec rake` completes without errors. 71 | 72 | #### Commit Changes 73 | 74 | Make sure git knows your name and email address: 75 | 76 | ``` 77 | git config --global user.name "Your Name" 78 | git config --global user.email "contributor@example.com" 79 | ``` 80 | 81 | Writing good commit logs is important. A commit log should describe what changed and why. 82 | 83 | ``` 84 | git add ... 85 | git commit 86 | ``` 87 | 88 | #### Push 89 | 90 | ``` 91 | git push origin my-feature-branch 92 | ``` 93 | 94 | #### Make a Pull Request 95 | 96 | Go to https://github.com/contributor/doppler and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. 97 | 98 | #### Rebase 99 | 100 | If you've been working on a change for a while, rebase with upstream/main. 101 | 102 | ``` 103 | git fetch upstream 104 | git rebase upstream/main 105 | git push origin my-feature-branch -f 106 | ``` 107 | 108 | We like neat commits, please try to amend your previous commits and force push the changes. 109 | 110 | ``` 111 | git commit --amend 112 | git push origin my-feature-branch -f 113 | ``` 114 | 115 | #### Check on Your Pull Request 116 | 117 | Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above. 118 | 119 | #### Be Patient 120 | 121 | It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there! 122 | 123 | #### Thank You 124 | 125 | Please do know that we really appreciate and value your time and work. We love you, really. 126 | -------------------------------------------------------------------------------- /app/views/v1/start/show.html.haml: -------------------------------------------------------------------------------- 1 | Partner API Documentation > General > Getting Started 2 | 3 | %h1 Getting Started 4 | 5 | Let's make a few API calls to obtain the information for Andy Warhol on behalf of an Artsy user with OAuth. 6 | 7 | //// authentication 8 | 9 | - if @error 10 | 11 | .alert.alert-danger 12 | There was an error connecting to the Artsy API: #{@error}. 13 | 14 | - elsif authenticated? 15 | .disabled 16 | %h4 1. You Already Have an Artsy Account 17 | 18 | You have already signed up to Artsy. If you wish, you may create a separate developer account at 19 | artsy.net/sign_up. 20 | 21 | %h4 2. Signed In 22 | 23 | You're already signed into this developer website. 24 | 25 | - else 26 | %h4 1. Create an Artsy Account 27 | 28 | Sign up for an account on Artsy. 29 | 30 | %h4 2. Sign In 31 | 32 | Sign in into the developer website. 33 | 34 | - if authenticated? && ! @selected_client_application && @client_applications && @client_applications.count > 1 35 | 36 | %h4 3. Select One of Your Apps 37 | 38 | You have #{pluralize(@client_applications.count, 'app')}. 39 | 40 | Choose one from the list: 41 | 42 | .select_app 43 | = bootstrap_form_tag url: '/v1/start', method: 'GET' do |f| 44 | = f.collection_select :id, @client_applications.map { |a| Hashie::Mash.new(id: a.id, label: "#{a.name} (#{a.client_id})") }, :id, :label, { prompt: true, hide_label: true }, { onchange: "var v = $('.select_app select option:selected').val(); window.location = '/v1/start?id=' + v;" } 45 | 46 | - elsif !@error && authenticated? && !@selected_client_application 47 | 48 | %h4 3. Create an App 49 | 50 | Create an app here. 51 | 52 | - elsif @selected_client_application && @selected_client_application.api_version != 1 53 | 54 | %h4 3. Request Partner Access 55 | 56 | Please note that the partner API is exclusively reserved for Artsy partners. Your #{@selected_client_application.name} app does not allow partner access. 57 | 58 | Contact support@artsy.net to request it. 59 | 60 | - elsif @selected_client_application && @selected_client_application.api_version == 1 61 | 62 | %h4 3. Use Your App 63 | 64 | %table.table.table-bordered.table-striped 65 | %tbody 66 | %tr 67 | %th.text-right Name 68 | %td= @selected_client_application.name 69 | %tr 70 | %th.text-right Client Id 71 | %td= @selected_client_application.client_id 72 | %tr 73 | %th.text-right Client Secret 74 | %td= @selected_client_application.client_secret 75 | %tr 76 | %th.text-right API Version 77 | %td= @selected_client_application.api_version 78 | %tr 79 | %th.text-right Redirect URLs 80 | %td= (@selected_client_application.redirect_urls || []).join("
") 81 | 82 | 83 | %h4 4. Implement OAuth Workflow 84 | 85 | The OAuth workflow enables your application to authenticate a user associated with a partner (eg. a gallery) with Artsy and to perform operations on the user's behalf (eg. upload an artwork). 86 | 87 | - redirect_url = @selected_client_application.redirect_urls.try(:first) 88 | - redirect_url = 'http://localhost' if redirect_url.blank? 89 | - authorize_url = "#{ArtsyApi::V1.url}/oauth2/authorize?client_id=#{@selected_client_application.client_id}&redirect_uri=#{CGI.escape(redirect_url)}&response_type=code" 90 | 91 | %h5 4.1 Redirect your users to #{authorize_url} 92 | 93 | %h5 4.2 Extract the code from the query string at #{redirect_url}. 94 | 95 | - access_token_url = "#{ArtsyApi::V1.url}/oauth2/access_token" 96 | %h5 4.3 Perform an HTTP POST to #{access_token_url} with code=..., client_id=#{@selected_client_application.client_id}, client_secret=#{@selected_client_application.client_secret} and grant_type=authorization_code. 97 | 98 | %h5 4.4 Extract access_token and expires_in from the JSON response. 99 | 100 | %h4 5. Obtain Logged-In User Information 101 | 102 | The authenticated user can retrieve their own identity from #{ArtsyApi::V1.root}/me and the list of partners under management (granted by an Artsy representative) from #{ArtsyApi::V1.root}/me/partners. 103 | 104 | %h4 6. Upload an Artwork 105 | 106 | Perform an HTTP POST to #{ArtsyApi::V1.root}/artwork with a title, artists (eg. ['andy-warhol']), remote_image_url and partner_id. 107 | 108 | %h4 7. Samples 109 | 110 | A set of working samples is available at https://github.com/artsy/artsy-api-v1-samples. 111 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.cache_classes = true 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 21 | # config.require_master_key = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? 26 | 27 | # Compress CSS using a preprocessor. 28 | # config.assets.css_compressor = :sass 29 | 30 | # Do not fallback to assets pipeline if a precompiled asset is missed. 31 | config.assets.compile = false 32 | 33 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 34 | # config.asset_host = 'http://assets.example.com' 35 | 36 | # Specifies the header that your server uses for sending files. 37 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 38 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 39 | 40 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 41 | # config.force_ssl = true 42 | 43 | # Include generic and useful information about system operation, but avoid logging too much 44 | # information to avoid inadvertent exposure of personally identifiable information (PII). 45 | config.log_level = :info 46 | 47 | # Prepend all log lines with the following tags. 48 | config.log_tags = [:request_id] 49 | 50 | # Use a different cache store in production. 51 | # config.cache_store = :mem_cache_store 52 | 53 | # Use a real queuing backend for Active Job (and separate queues per environment). 54 | # config.active_job.queue_adapter = :resque 55 | # config.active_job.queue_name_prefix = "doppler_production" 56 | 57 | config.action_mailer.perform_caching = false 58 | 59 | # Ignore bad email addresses and do not raise email delivery errors. 60 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 61 | # config.action_mailer.raise_delivery_errors = false 62 | 63 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 64 | # the I18n.default_locale when a translation cannot be found). 65 | config.i18n.fallbacks = true 66 | 67 | # Send deprecation notices to registered listeners. 68 | config.active_support.deprecation = :notify 69 | 70 | # Log disallowed deprecations. 71 | config.active_support.disallowed_deprecation = :log 72 | 73 | # Tell Active Support which deprecation messages to disallow. 74 | config.active_support.disallowed_deprecation_warnings = [] 75 | 76 | # Use default logging formatter so that PID and timestamp are not suppressed. 77 | config.log_formatter = ::Logger::Formatter.new 78 | 79 | # Use a different logger for distributed setups. 80 | # require "syslog/logger" 81 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 82 | 83 | if ENV["RAILS_LOG_TO_STDOUT"].present? 84 | logger = ActiveSupport::Logger.new($stdout) 85 | logger.formatter = config.log_formatter 86 | config.logger = ActiveSupport::TaggedLogging.new(logger) 87 | end 88 | 89 | # Inserts middleware to perform automatic connection switching. 90 | # The `database_selector` hash is used to pass options to the DatabaseSelector 91 | # middleware. The `delay` is used to determine how long to wait after a write 92 | # to send a subsequent read to the primary. 93 | # 94 | # The `database_resolver` class is used by the middleware to determine which 95 | # database is appropriate to use based on the time delay. 96 | # 97 | # The `database_resolver_context` class is used by the middleware to set 98 | # timestamps for the last write to the primary. The resolver uses the context 99 | # class timestamps to determine how long to wait before reading from the 100 | # replica. 101 | # 102 | # By default Rails will store a last write timestamp in the session. The 103 | # DatabaseSelector middleware is designed as such you can define your own 104 | # strategy for connection switching and pass that into the middleware through 105 | # these configuration options. 106 | # config.active_record.database_selector = { delay: 2.seconds } 107 | # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver 108 | # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session 109 | end 110 | -------------------------------------------------------------------------------- /spec/features/client_applications_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Client Applications" do 4 | before do 5 | allow(ArtsyApi::V2).to receive_messages( 6 | artworks_count: 123, 7 | xapp_token: "foo" 8 | ) 9 | end 10 | it "requires authentication" do 11 | expect_any_instance_of(ApplicationController).to receive(:require_artsy_authentication) 12 | visit "/client_applications" 13 | end 14 | context "logged in" do 15 | before do 16 | allow_any_instance_of(ApplicationController).to receive(:require_artsy_authentication) 17 | end 18 | context "without applications" do 19 | before do 20 | allow(ArtsyApi::V2).to receive_message_chain(:client, :applications).and_return([]) 21 | end 22 | end 23 | context "with an application" do 24 | let(:application) do 25 | Hashie::Mash.new( 26 | id: "1", 27 | name: "One", 28 | client_id: "client_id", 29 | client_secret: "client_secret", 30 | redirect_urls: [], 31 | created_at: Time.now.utc.to_s, 32 | updated_at: Time.now.utc.to_s, 33 | _attributes: { 34 | id: "1" 35 | } 36 | ) 37 | end 38 | before do 39 | allow(ArtsyApi::V2).to receive_message_chain(:client, :applications).and_return([application]) 40 | allow(ArtsyApi::V2).to receive_message_chain(:client, :application).and_return(application) 41 | end 42 | it "displays the app in a table" do 43 | visit "/client_applications" 44 | expect(page).to have_css "td.name", text: "One" 45 | expect(page).to have_css "td.client_id", text: "client_id" 46 | end 47 | it "displays the app" do 48 | visit "/client_applications/123" 49 | expect(page).to have_css "td#name", text: "One" 50 | expect(page).to have_css "td#client_id", text: "client_id" 51 | expect(page).to have_css "td#client_secret", text: "client_secret" 52 | end 53 | it "edits the app" do 54 | expect(application).to receive(:_put).with(name: "Updated", redirect_urls: ["http://example.org"]) 55 | visit "/client_applications" 56 | click_link "edit" 57 | fill_in "Name", with: "Updated" 58 | fill_in "Redirect urls", with: "http://example.org" 59 | click_button "Save" 60 | end 61 | it "destroys the app" do 62 | expect(application).to receive(:_delete) 63 | visit "/client_applications" 64 | click_link "destroy" 65 | click_link "OK" 66 | end 67 | end 68 | context "with an application with enabled=false" do 69 | let(:application) do 70 | Hashie::Mash.new( 71 | id: "1", 72 | name: "One", 73 | client_id: "client_id", 74 | client_secret: "client_secret", 75 | redirect_urls: [], 76 | created_at: Time.now.utc.to_s, 77 | updated_at: Time.now.utc.to_s, 78 | enabled: false, 79 | _attributes: { 80 | id: "1" 81 | } 82 | ) 83 | end 84 | before do 85 | allow(ArtsyApi::V2).to receive_message_chain(:client, :applications).and_return([application]) 86 | allow(ArtsyApi::V2).to receive_message_chain(:client, :application).and_return(application) 87 | end 88 | it "displays the app access in a table" do 89 | visit "/client_applications" 90 | expect(page).to have_css "td.enabled", text: "No" 91 | end 92 | it "displays the app" do 93 | visit "/client_applications/123" 94 | expect(page).to have_css "td#enabled", text: "No" 95 | end 96 | it "edits the app" do 97 | visit "/client_applications" 98 | click_link "edit" 99 | expect(page).to have_css "td#enabled", text: "No" 100 | end 101 | end 102 | context "with an application with published_artworks_access_enabled" do 103 | let(:application) do 104 | Hashie::Mash.new( 105 | id: "1", 106 | name: "One", 107 | client_id: "client_id", 108 | client_secret: "client_secret", 109 | redirect_urls: [], 110 | created_at: Time.now.utc.to_s, 111 | updated_at: Time.now.utc.to_s, 112 | enabled: true, 113 | published_artworks_access_enabled: true, 114 | _attributes: { 115 | id: "1" 116 | } 117 | ) 118 | end 119 | before do 120 | allow(ArtsyApi::V2).to receive_message_chain(:client, :applications).and_return([application]) 121 | allow(ArtsyApi::V2).to receive_message_chain(:client, :application).and_return(application) 122 | end 123 | it "displays the app access in a table" do 124 | visit "/client_applications" 125 | expect(page).to have_css "td.enabled", text: "Yes + Published Artworks" 126 | end 127 | it "displays the app" do 128 | visit "/client_applications/123" 129 | expect(page).to have_css ".alert.alert-success", text: "This application has access to all published artworks." 130 | expect(page).to have_css "td#enabled", text: "Yes" 131 | end 132 | it "edits the app" do 133 | visit "/client_applications" 134 | click_link "edit" 135 | expect(page).to have_css ".alert.alert-success", text: "This application has access to all published artworks." 136 | expect(page).to have_css "td#enabled", text: "Yes" 137 | end 138 | end 139 | end 140 | end 141 | --------------------------------------------------------------------------------