In the Summer of 2014, we began developing a bidding kiosk for the Artsy auctions platform (code-named Eidolon). Typically, the iOS team here at Artsy worked on two main apps: a consumer-facing iPhone app and an iPad app used by art galleries. For Eidolon, we followed Artsy’s standard practices for building our software and use GitHub issues as our canonical source for bug reports and feature requests. Many of the components used in our apps are open source, but the codebases themselves remain in private repositories.

Initial planning for Eidolon began over the Summer. Our designer Katarina had the main features sketched out. I was scheduled to work on it at first, with Orta and Laura joining me near the end of the project. We had a rough scope: the app would be able to list artworks at an auction and allow prospective bidders to learn more about these artworks. The user would be able to register to bid and place bids using the Kiosk, including credit card processing for identity-checking.

Read on →

This post is about how, in a week, we switched from Solr to Google Site Search and customized it into a fast, beautiful search service. Search is a difficult problem – a really difficult problem. For small companies and startups, the common solution to search is to launch a custom search service based on Solr or Elastic Search. While these services are very appropriate for private data, we think Google Site Search should be considered in addition to these services for a public website. It is often not considered because users search on a dedicated site with different intent than they search Google. We found that while this may be true, it is not necessarily a good reason to roll your own search service for your public site.

Search for photography on artsy

Read on →

Many established companies have bug bounty programs, including a recently publicized Twitter Bug Bounty. Some use services, such as HackerOne or BugCrowd. In early September 2014 we quietly launched our own Security Bug Bounty. Since then we have fixed 14 issues reported by 15 security researchers and paid $750 in bounty. In the process we have learned a ton and wanted to share some things that would have probably done a little bit differently, knowing what we know now.

In this post I will focus on both technical and non-technical takeaways, and will provide an extensive list of vulnerabilities that should have been dealt with before launching our bug bounty.

Read on →

Introduction

The Indianapolis Museum of Art (IMA) recently shared thousands of high-resolution images from its permanent collection with Artsy, including 5,000 images that were in an un-cropped state. These images contain color swatches, frames, and diverse backgrounds, as shown below. The clutter in these images made them inappropriate to display to end users, and invited an approach to automatically crop the images. This post explores some fully automated techniques to locate the piece of art within each photo. If you’re eager to jump straight to the code that worked best, you’ll find the implementation of the ‘Rectangular Contour Search’ section here.

All of these images are open access. For reference, the accession numbers/names. 45-115.tif 45-9-v01.tif 54-4.tif 76-166-1-12b.tif 14-57.tif

Read on →

Artsy’s API requires something called an XApp token in order to perform requests. The token comes back with an expiry date, after which the token will no longer work. A new token will have to be fetched.

{
	"xapp_token": "SOME_TOKEN",
	"expires_in":"2014-09-19T12:22:21.570Z"
}

In our previous iOS apps, tragically written in Objective-C, we have a lot of code that looks like the following. getXappTokenWithCompletion: checks to make sure that there is a valid token. If there is, it invokes the completion block immediately. Otherwise, it fetches a token, sets it in a static variable, and then invokes the completion block.

[ArtsyAPI getXappTokenWithCompletion:^(NSString *xappToken, NSDate *expirationDate) {
    [ArtsyAPI getSomething:^(NSDictionary *results) {
       // do something
    } failure:^(NSError *error) {
        // handle herror
    }];
}];

That’s kind of ugly. A better approach might be to embed the token-requesting logic within the getSomething: method. But that kind of sucks, since we’d have to reproduce that logic for every network-accessing method. If we have ten methods, that’s ten times we need to duplicate that logic.

With our new app (written in Swift), we’re using a network abstraction layer we’ve created called Moya. Moya sits on top of Alamofire and provides an abstraction for API endpoints. Instead of having ten different network-accessing methods, there is only one method to which you pass one of the ten different possible enum values. This means you have compile-time safety in your networking code, but that’s not really what we’re here to talk about.

Read on →

Today we are happy to announce that we’re making a new public API generally available, along with over 26,000 artworks from many of our institutional partners.

The Artsy API currently provides access to images of historic artwork and related information on artsy.net for educational and other non-commercial purposes. You can try it for playing, testing, and learning, but not yet for production. The scope of the API will expand in the future as it gains some traction.

If you just want to use the API, you can stop reading here and head to the developers.artsy.net website. (The developers website itself is a classic Rails + Bootstrap example and is also open-source.)

In this post we will step back and describe some of the technical decisions made during the development of the new API.

Read on →

May The Force be With You

Today we’re happy to announce we’ve open sourced the entire Artsy.net web app, Force.

Over the past few months, we’ve rewritten our web front-end to move off Rails and on to a Node.js stack that shares Javascript code and rendering between the server and client, otherwise known as Isomorphic Javascript. After migrating to this new stack, we open-sourced our boilerplate, Ezel, and have now gone a step further and open sourced Artsy.net.

Read on →

At Artsy, we try hard to test our iOS applications to ensure that we avoid regressions and have a clearly defined spec of how our apps should look and behave. One of the core pieces of our testing setup is FBSnapshotTestCase, a library written by Facebook to compare views at runtime with images of those views that are known to be correct. If the images differ, the test fails. We also use Travis for continuous integration.

Lately, we’ve been noticing a friction between the developers on the iOS team and the tools we’re using to test our apps: while Travis allows us to easily access the logs of test runs, it can only indicate that a snapshot test failed, not why it failed. That’s because the images that are compared are locked on Travis’ machine – we cannot access those images, so we can’t see the differences. This is really promblematic when the tests pass locally but fail only on Travis.

Read on →

Analytics are common in iOS applications. They help inform our decisions about products. Since analytics are so common, Artsy developed a library called ARAnalytics. This library provides a single interface to many different backend analytics providers, freeing developers from having to write code for each of the providers that they’re using.

Let’s consider a typical view controller on iOS. View controllers on iOS represent the glue code between models and views. When a model changes, the view controller updates the appearance of the UI. Similarly, when the UI is interacted with by the user, the view controller updates the model. This is the core of any standard iOS application.

So let’s say that a button is pressed. We’ll handle that interaction in a method called buttonWasPressed:. We’ll want to update our model, but also to track the analytics event.

- (void)buttonWasPressed:(id)sender
{
	self.model.hearted = YES;

	[ARAnalytics event:@"hearted"];
}

Simple enough, but consider that the analytics tracking code doesn’t fall within our definition of a view controller – the button handler just happens to be a convenient place to put the tracking code. Also consider that every single button handler is going to have to have similar code implemented.

There has to be a better way.

Read on →