Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 117 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,93 @@
## Introduction

Thanks for applying for a development role at Fatsoma. To give us a good
indication of programming ability and style please submit your solution for
this ticket allocation problem.
indication of your programming ability and style, please submit your solution
for this ticket allocation problem. We are mainly looking for clean, well
architected, tested code that highlights your skill set and shows technical
proficiency.

This is not a timed test, but you do not need to spend more than a couple of
hours on this. A partial solution is still very useful, and you can describe
your thoughts for next steps that would be taken.
hours on it. We have tried to keep the scope small so that time is spent on the
interesting parts rather than on boilerplate. A partial solution is still very
useful — if you run out of time, describe in your submission what you would do
next.

Your submission must be your own work.

### Use of AI tools

Using AI tools (e.g. GitHub Copilot, ChatGPT, Claude) is allowed — they are part
of how we work day to day. However, please **declare any AI co-authorship** in
your submission notes: briefly note where and how you used AI (for example,
scaffolding, generating tests, or rubber-ducking the locking approach). You
remain responsible for everything you submit, so make sure you understand and
can explain your solution — we will likely discuss it when we meet.


### Languages and frameworks

For reference, here at Fatsoma we primarily develop using ruby on rails and
golang. However feel free to solve this in whatever language you feel comfortable with and use a framework if desired.
For reference, here at Fatsoma we primarily develop using Ruby on Rails and
Go. However, feel free to solve this in whatever language you are most
comfortable with, and use a framework if you wish. We are interested in how you
structure and reason about the solution, not in any particular stack.

We are mainly looking for clean, well architected, tested code that highlights
your skill set and shows technical proficiency.
## Problem definition

### Database
The following three routes need to be built to enable allocating ticket options
to multiple purchases. They all correspond to the [JSON:API spec](https://jsonapi.org/).
If helpful, a list of JSON:API implementation libraries across various languages
is available at https://jsonapi.org/implementations/. We're not assessing strict
conformance to the spec, so minor differences in the output produced by your
chosen library are fine.

Included is an SQL dump from PostgreSQL. It is not required to use this but
should be helpful. You may need to amend this to add constraints.
A `ticket_option` is created with a fixed `allocation` — the total number of
tickets available to purchase. Each `purchase` draws a `quantity` of tickets
against a `ticket_option`.

PostgreSQL has all the functionality required for satisfying this problem set,
with some features introduced version 9.5 that may be of interest. You may
choose to use a different database engine that satisfies the requirements of
this problem.
The solution must guarantee the following invariant at all times:

---
> The sum of purchased quantities for a `ticket_option` must never exceed its
> `allocation`, and the remaining availability must never drop below 0.

## Problem definition
The `allocation` value on a `ticket_option` represents its **total** capacity
and does not change once created; remaining availability is derived from the
allocation minus the quantities already purchased. The `GET` route returns the
`ticket_option` as created (i.e. the original `allocation`).

The following three routes need to be built to enable allocating of ticket
options to multiple purchases. They all correspond to the jsonapi spec (https://jsonapi.org/)
Expect requests to be made against this API concurrently, and design it to scale
horizontally: multiple instances of the program may run at once to handle large
numbers of requests. The invariant above must hold regardless of how many
requests, or how many running instances, are involved — including when many
purchases hit the same `ticket_option` at the same time.

The solution needs to ensure that the allocation does not drop below 0,
and the purchased amounts are not greater than the allocation given.
We use the term "purchase", but taking payment is out of scope. You do not need
to build authentication, payment handling, a UI, or anything beyond the three
routes described below.

Expect multiple requests to be made against this API concurrently.
### Database

Included is an SQL dump from PostgreSQL (`database_structure.sql`). You are not
required to use it, but it should be a helpful starting point. Note that it
intentionally ships **without** integrity constraints (no foreign keys, nullable
columns, no `CHECK`s) — part of the exercise is deciding what constraints the
schema should have.

We use the term purchase but taking payment is out of scope for this problem.
The SQL dump is provided purely as an example. Whatever storage you use, it must
let you prevent overselling a `ticket_option`'s allocation under concurrent load
across multiple instances. Beyond that, you may use any database engine or
storage approach you like.

## Routes with Example Requests
## Routes with example requests

The Swagger definition and postman collection are also available in this repository for reference.
All requests and responses use the JSON:API media type
`application/vnd.api+json`. The Swagger definition
(`Ticket_Allocation.swagger.yaml`) and Postman collection
(`Ticket_Allocation.postman_collection.json`) are also available in this
repository for reference.

### Create Ticket Option
### Create ticket option

Create a ticket_option with an allocation of tickets available to purchase:
Create a `ticket_option` with an allocation of tickets available to purchase:

`POST /ticket_options`

Expand All @@ -69,7 +108,7 @@ Request Body:
}
```

Response Body:
Response Body (`201 Created`):

```json
{
Expand All @@ -85,15 +124,18 @@ Response Body:
}
```

### Get Ticket Option
`allocation` must be a non-negative integer. Requests with a missing `name` or a
negative/non-integer `allocation` should return a `4xx` error.

### Get ticket option

Get ticket option by id:

`GET /ticket_options/:id`

(No request body)

Response Body:
Response Body (`200 OK`):

```json
{
Expand All @@ -109,9 +151,13 @@ Response Body:
}
```

### Purchase from Ticket Option
A request for an id that does not exist should return a `404 Not Found`.

Purchase a quantity of tickets from the allocation of the given ticket_option and associate with a user (N.B. managing a user resource is not being looked at here, so an example id should be sufficient):
### Purchase from ticket option

Purchase a quantity of tickets from the allocation of the given `ticket_option`
and associate it with a user (N.B. managing a user resource is not being looked
at here, so an example id is sufficient):

`POST /purchases`

Expand Down Expand Up @@ -140,12 +186,11 @@ Request body:
}
}
}

```

Response Body:

A 2xx status code must be returned on success.
A `201 Created` status code must be returned on success.

```json
{
Expand Down Expand Up @@ -173,7 +218,9 @@ A 2xx status code must be returned on success.
}
```

A 4xx status code must be returned on any request that attempts to purchase more tickets than are available. In this case, no tickets should be purchased for that request. Example error response given below
A `4xx` status code must be returned on any request that attempts to purchase
more tickets than are available. In this case, no tickets should be purchased
for that request. Example error response given below:

```json
{
Expand All @@ -190,3 +237,38 @@ A 4xx status code must be returned on any request that attempts to purchase more
]
}
```

### Validation and error handling

Please handle at least the following cases with an appropriate `4xx` status and
a JSON:API `errors` payload (the shape above is a good template):

- `quantity` that is zero or negative.
- A purchase referencing a `ticket_option` id that does not exist
(`404 Not Found`).
- A request that would exceed the remaining allocation (`code:
invalid_purchase_quantity`, as above) — no tickets persisted.
- Missing required attributes on create (e.g. `name`, `allocation`).

You do not need to exhaustively validate every field; we are more interested in
seeing a consistent, sensible approach than full coverage.

## Submitting your solution

Please include a short README in your submission covering:

1. **How to run it** — setup steps, dependencies, and how to start the API
(including any database setup/migrations).
2. **How to run the tests.**
3. **Notes** — any assumptions you made, trade-offs, what you would do next
given more time, and a short declaration of any AI tool usage (see
[Use of AI tools](#use-of-ai-tools)).

Submit your solution as a link to a Git repository (e.g. GitHub) or as a
zip/tarball, whichever is easier for you. If you use a private repository, please
invite the GitHub user `fatsoma-review` so we can access it. Please do not
include compiled artifacts or dependency directories (e.g. `node_modules`,
`vendor`).

If anything in this brief is unclear, make a reasonable assumption, state it in
your notes, and carry on — we are happy to discuss your reasoning when we meet.
32 changes: 22 additions & 10 deletions Ticket_Allocation.swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ info:
version: 1.0.0
contact: {}
servers:
- url: localhost
- url: http://localhost:3000
paths:
/ticket_options/{ticketOptionID}:
get:
Expand All @@ -13,7 +13,9 @@ paths:
operationId: getTicketOption
responses:
'200':
description: ''
description: Ticket option found
'404':
description: Ticket option not found
parameters:
- name: ticketOptionID
in: path
Expand All @@ -27,7 +29,7 @@ paths:
operationId: createTicketOption
requestBody:
content:
application/json:
application/vnd.api+json:
schema:
type: object
properties:
Expand All @@ -38,7 +40,8 @@ paths:
type: object
properties:
allocation:
type: number
type: integer
minimum: 0
example: 100
description:
type: string
Expand All @@ -59,8 +62,10 @@ paths:
name: Test ticket option
type: ticket_options
responses:
'200':
description: ''
'201':
description: Ticket option created
'400':
description: Invalid request body
/purchases:
post:
summary: Create Purchase
Expand All @@ -70,7 +75,7 @@ paths:
operationId: createPurchase
requestBody:
content:
application/json:
application/vnd.api+json:
schema:
type: object
properties:
Expand All @@ -81,7 +86,8 @@ paths:
type: object
properties:
quantity:
type: number
type: integer
minimum: 1
example: 1
relationships:
type: object
Expand Down Expand Up @@ -130,6 +136,12 @@ paths:
type: users
type: purchases
responses:
'200':
description: ''
'201':
description: Purchase created
'400':
description: >-
Invalid purchase (e.g. quantity exceeds remaining allocation or is
not a positive integer); no tickets are persisted
'404':
description: Referenced ticket option not found
tags: []