base on Sell stuff and see what sticks <p align="center">
<picture>
<source srcset="https://public-files.gumroad.com/logo/gumroad-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="https://public-files.gumroad.com/logo/gumroad.svg" media="(prefers-color-scheme: light)">
<img src="https://public-files.gumroad.com/logo/gumroad.svg" height="100" alt="Gumroad logo">
</picture>
</p>
<p align="center">
<strong>Sell your stuff. See what sticks.</strong>
</p>
<p align="center">
<a href="https://gumroad.com">Gumroad</a> is an e-commerce platform that enables creators to sell products directly to consumers. This repository contains the source code for the Gumroad web application.
</p>
## Table of Contents
- [Getting Started](#getting-started)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Configuration](#configuration)
- [Running Locally](#running-locally)
- [Development](#development)
- [Logging in](#logging-in)
- [Resetting Elasticsearch indices](#resetting-elasticsearch-indices)
- [Push Notifications](#push-notifications)
- [Common Development Tasks](#common-development-tasks)
- [Linting](#linting)
## Getting Started
### Prerequisites
> 💡 If you're on Windows, follow our [Windows setup guide](docs/development/windows.md) instead.
Before you begin, ensure you have the following installed:
#### Ruby
- https://www.ruby-lang.org/en/documentation/installation/
- Install the version listed in [the .ruby-version file](./.ruby-version)
#### Node.js
- https://nodejs.org/en/download
- Install the version listed in [the .node-version file](./.node-version)
#### Docker
We use Docker to setup the services for development environment.
- For MacOS: Download the Docker app from the [Docker website](https://www.docker.com/products/docker-desktop)
- For Linux:
```bash
sudo wget -qO- https://get.docker.com/ | sh
sudo usermod -aG docker $(whoami)
```
#### MySQL & Percona Toolkit
Install a local version of MySQL 8.0.x to match the version running in production.
The local version of MySQL is a dependency of the Ruby `mysql2` gem. You do not need to start an instance of the MySQL service locally. The app will connect to a MySQL instance running in the Docker container.
- For MacOS:
```bash
brew install
[email protected] percona-toolkit
brew link --force
[email protected]
# to use Homebrew's `openssl`:
brew install openssl
bundle config --global build.mysql2 --with-opt-dir="$(brew --prefix openssl)"
# ensure MySQL is not running as a service
brew services stop
[email protected]
```
- For Linux:
- MySQL:
- https://dev.mysql.com/doc/refman/8.0/en/linux-installation.html
- `apt install libmysqlclient-dev`
- Percona Toolkit: https://www.percona.com/doc/percona-toolkit/LATEST/installation.html
#### Image Processing Libraries
##### ImageMagick
We use `imagemagick` for preview editing.
- For MacOS: `brew install imagemagick`
- For Linux: `sudo apt-get install imagemagick`
##### libvips
For newer image formats we use `libvips` for image processing with ActiveStorage.
- For MacOS: `brew install libvips`
- For Linux: `sudo apt-get install libvips-dev`
#### FFmpeg
We use `ffprobe` that comes with `FFmpeg` package to fetch metadata from video files.
- For MacOS: `brew install ffmpeg`
- For Linux: `sudo apt-get install ffmpeg`
#### PDFtk
We use [pdftk](https://www.pdflabs.com/tools/pdftk-server/) to stamp PDF files with the Gumroad logo and the buyers' emails.
- For MacOS: Download from [here](https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/pdftk_server-2.02-mac_osx-10.11-setup.pkg)
- **Note:** pdftk may be blocked by Apple's firewall. If this happens, go to Settings > Privacy & Security and click "Open Anyways" to allow the installation.
- For Linux: `sudo apt-get install pdftk`
#### wkhtmltopdf
While generating invoices, to convert HTML to PDF, PDFKit expects [wkhtmltopdf](https://wkhtmltopdf.org/) to be installed on your system. [Download](https://wkhtmltopdf.org/downloads.html) and install the version 0.12.6 for your platform.
- **Note** similar to pdftk, this may also be blocked by Apple's firewall on MacOS. Follow a similar process as above.
### Installation
#### Bundler and gems
We use Bundler to install Ruby gems.
```shell
gem install bundler
```
Install gems:
```shell
bundle install
```
Also make sure to install `dotenv` as it is required for some console commands:
```shell
gem install dotenv
```
#### npm and Node.js dependencies
Make sure the correct version of `npm` is enabled:
```shell
corepack enable
```
Install dependencies:
```shell
npm install
```
### Configuration
#### Set up Custom credentials
App can be booted without any custom credentials. But if you would like to use services that require custom credentials (e.g. S3, Stripe, Resend, etc.), you can copy the `.env.example` file to `.env` and fill in the values.
#### S3 Bucket Setup
After configuring your AWS credentials, you need to create the specific S3 buckets required for development. The application uses hardcoded bucket names as defined in `config/initializers/aws.rb`:
**Required S3 Buckets:**
- `gumroad_dev` - Main storage bucket for development
- `gumroad-dev-public-storage` - Public storage bucket for development
**Create the buckets using AWS CLI:**
```bash
aws s3 mb s3://gumroad_dev
aws s3 mb s3://gumroad-dev-public-storage
```
**Or create them via AWS Console:**
1. Go to the [S3 Console](https://console.aws.amazon.com/s3/)
2. Click "Create bucket"
3. Enter bucket name: `gumroad_dev`
4. Choose your preferred region (should match `AWS_DEFAULT_REGION`)
5. Keep default settings and create the bucket
6. Repeat steps 2-5 for `gumroad-dev-public-storage`
> **Note:** These exact bucket names are required because they are hardcoded in the application configuration. Using different names will result in `AccessDenied` errors during file uploads.
#### Local SSL Certificates
1. Install mkcert on macOS:
```shell
brew install mkcert
```
For other operating systems, see [mkcert installation instructions](https://github.com/FiloSottile/mkcert?tab=readme-ov-file#installation).
2. Generate certificates by running:
```shell
bin/generate_ssl_certificates
```
### Running Locally
#### Start Docker services
If you installed Docker Desktop (on a Mac or Windows machine), you can run the following command to start the Docker services:
```shell
make local
```
If you are on Linux, or installed Docker via a package manager on a mac, you may have to manually give docker superuser access to open ports 80 and 443. To do that, use `sudo make local` instead.
This command will not terminate. You run this in one tab and start the application in another tab.
If you want to run Docker services in the background, use `LOCAL_DETACHED=true make local` instead.
#### Set up the database
```shell
bin/rails db:prepare
```
For Linux (Debian / Ubuntu) you might need the following:
- `apt install libxslt-dev libxml2-dev`
#### Start the application
```shell
bin/dev
```
This starts the Rails server, the JavaScript build system, and a Sidekiq worker.
You can now access the application at `https://gumroad.dev`.
## Development
### Logging in
You can log in with the username `
[email protected]` and the password `password`. The two-factor authentication code is `000000`.
Read more about logging in as a user with a different team role at [Users & authentication](docs/users.md).
### Resetting Elasticsearch indices
You will need to explicitly reindex Elasticsearch to populate the indices after setup, otherwise you will see `index_not_found_exception` errors when you visit the dev application. You can reset them using:
```ruby
# Run this in a rails console:
DevTools.delete_all_indices_and_reindex_all
```
### Push Notifications
To send push notifications:
```shell
INITIALIZE_RPUSH_APPS=true bundle exec rpush start -e development -f
```
### Common Development Tasks
#### Rails console:
```shell
bin/rails c
```
#### Rake tasks:
```shell
bin/rake task_name
```
### Linting
We use ESLint for JS, and Rubocop for Ruby. Your editor should support displaying and fixing issues reported by these inline, and CI will automatically check and fix (if possible) these.
If you'd like, you can run `git config --local core.hooksPath .githooks` to check for these locally when committing.
## Common Issues
### macOS Error When Running Tests (Related to `fork()`)
```
objc[11912]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called.
objc[11912]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
```
This issue occurs on macOS due to how the `fork()` system call interacts with multithreaded Objective-C applications—commonly triggered when Spring is enabled during testing.
#### How to Fix:
Temporarily disable Spring before running your tests to avoid this error.
```bash
export DISABLE_SPRING=1
bin/rspec spec/requests/balance_pages_spec.rb
```
This will disable Spring for the current session, allowing the tests to run without triggering the `fork()`-related crash.
", Assign "at most 3 tags" to the expected json: {"id":"14196","tags":[]} "only from the tags list I provide: [{"id":77,"name":"3d"},{"id":89,"name":"agent"},{"id":17,"name":"ai"},{"id":54,"name":"algorithm"},{"id":24,"name":"api"},{"id":44,"name":"authentication"},{"id":3,"name":"aws"},{"id":27,"name":"backend"},{"id":60,"name":"benchmark"},{"id":72,"name":"best-practices"},{"id":39,"name":"bitcoin"},{"id":37,"name":"blockchain"},{"id":1,"name":"blog"},{"id":45,"name":"bundler"},{"id":58,"name":"cache"},{"id":21,"name":"chat"},{"id":49,"name":"cicd"},{"id":4,"name":"cli"},{"id":64,"name":"cloud-native"},{"id":48,"name":"cms"},{"id":61,"name":"compiler"},{"id":68,"name":"containerization"},{"id":92,"name":"crm"},{"id":34,"name":"data"},{"id":47,"name":"database"},{"id":8,"name":"declarative-gui "},{"id":9,"name":"deploy-tool"},{"id":53,"name":"desktop-app"},{"id":6,"name":"dev-exp-lib"},{"id":59,"name":"dev-tool"},{"id":13,"name":"ecommerce"},{"id":26,"name":"editor"},{"id":66,"name":"emulator"},{"id":62,"name":"filesystem"},{"id":80,"name":"finance"},{"id":15,"name":"firmware"},{"id":73,"name":"for-fun"},{"id":2,"name":"framework"},{"id":11,"name":"frontend"},{"id":22,"name":"game"},{"id":81,"name":"game-engine "},{"id":23,"name":"graphql"},{"id":84,"name":"gui"},{"id":91,"name":"http"},{"id":5,"name":"http-client"},{"id":51,"name":"iac"},{"id":30,"name":"ide"},{"id":78,"name":"iot"},{"id":40,"name":"json"},{"id":83,"name":"julian"},{"id":38,"name":"k8s"},{"id":31,"name":"language"},{"id":10,"name":"learning-resource"},{"id":33,"name":"lib"},{"id":41,"name":"linter"},{"id":28,"name":"lms"},{"id":16,"name":"logging"},{"id":76,"name":"low-code"},{"id":90,"name":"message-queue"},{"id":42,"name":"mobile-app"},{"id":18,"name":"monitoring"},{"id":36,"name":"networking"},{"id":7,"name":"node-version"},{"id":55,"name":"nosql"},{"id":57,"name":"observability"},{"id":46,"name":"orm"},{"id":52,"name":"os"},{"id":14,"name":"parser"},{"id":74,"name":"react"},{"id":82,"name":"real-time"},{"id":56,"name":"robot"},{"id":65,"name":"runtime"},{"id":32,"name":"sdk"},{"id":71,"name":"search"},{"id":63,"name":"secrets"},{"id":25,"name":"security"},{"id":85,"name":"server"},{"id":86,"name":"serverless"},{"id":70,"name":"storage"},{"id":75,"name":"system-design"},{"id":79,"name":"terminal"},{"id":29,"name":"testing"},{"id":12,"name":"ui"},{"id":50,"name":"ux"},{"id":88,"name":"video"},{"id":20,"name":"web-app"},{"id":35,"name":"web-server"},{"id":43,"name":"webassembly"},{"id":69,"name":"workflow"},{"id":87,"name":"yaml"}]" returns me the "expected json"