Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ruby 3.4.1
nodejs 23.8.0
10 changes: 10 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
source "https://rubygems.org"

ruby "3.4.1"
ruby "3.4.1"
gem "rails", "~> 8.0.1"

gem "pg"
gem "activerecord-postgis-adapter"

gem "pg"
gem "activerecord-postgis-adapter"
gem "puma", ">= 5.0"
Expand All @@ -15,6 +19,8 @@ gem "dry-types"
gem "ransack"
gem "kaminari"

gem "rswag-ui"
gem "openapi_first"
gem "jsonapi-serializer"

group :development, :test do
Expand All @@ -27,4 +33,8 @@ group :development, :test do
gem "factory_bot_rails"
gem "faker"
gem "rspec-json_expectations"
gem "rspec-rails"
gem "factory_bot_rails"
gem "faker"
gem "rspec-json_expectations"
end
80 changes: 80 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ GEM
activerecord-postgis-adapter (11.0.0)
activerecord (~> 8.0.0)
rgeo-activerecord (~> 8.0.0)
activerecord-postgis-adapter (11.0.0)
activerecord (~> 8.0.0)
rgeo-activerecord (~> 8.0.0)
activestorage (8.0.1)
actionpack (= 8.0.1)
activejob (= 8.0.1)
Expand Down Expand Up @@ -92,12 +95,35 @@ GEM
irb (~> 1.10)
reline (>= 0.3.8)
diff-lcs (1.6.0)
diff-lcs (1.6.0)
drb (2.2.1)
dry-core (1.1.0)
concurrent-ruby (~> 1.0)
logger
zeitwerk (~> 2.6)
dry-inflector (1.2.0)
dry-logic (1.6.0)
bigdecimal
concurrent-ruby (~> 1.0)
dry-core (~> 1.1)
zeitwerk (~> 2.6)
dry-struct (1.7.1)
dry-core (~> 1.1)
dry-types (~> 1.8, >= 1.8.2)
ice_nine (~> 0.11)
zeitwerk (~> 2.6)
dry-types (1.8.2)
bigdecimal (~> 3.0)
concurrent-ruby (~> 1.0)
dry-core (~> 1.0)
dry-inflector (~> 1.0)
dry-logic (~> 1.4)
zeitwerk (~> 2.6)
dry-core (1.1.0)
concurrent-ruby (~> 1.0)
logger
zeitwerk (~> 2.6)
dry-inflector (1.2.0)
dry-logic (1.6.0)
bigdecimal
concurrent-ruby (~> 1.0)
Expand All @@ -116,6 +142,13 @@ GEM
dry-logic (~> 1.4)
zeitwerk (~> 2.6)
erubi (1.13.1)
factory_bot (6.5.1)
activesupport (>= 6.1.0)
factory_bot_rails (6.4.4)
factory_bot (~> 6.5)
railties (>= 5.0.0)
faker (3.5.1)
i18n (>= 1.8.11, < 2)
factory_bot (6.5.1)
activesupport (>= 6.1.0)
factory_bot_rails (6.4.4)
Expand All @@ -125,15 +158,22 @@ GEM
i18n (>= 1.8.11, < 2)
globalid (1.2.1)
activesupport (>= 6.1)
hana (1.3.7)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
ice_nine (0.11.2)
io-console (0.8.0)
irb (1.15.1)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
json (2.10.1)
json_schemer (2.4.0)
bigdecimal
hana (~> 1.3)
regexp_parser (~> 2.0)
simpleidn (~> 0.2)
jsonapi-serializer (2.2.0)
activesupport (>= 4.2)
kaminari (1.2.2)
Expand All @@ -148,6 +188,18 @@ GEM
activerecord
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
kaminari (1.2.2)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2)
kaminari-activerecord (= 1.2.2)
kaminari-core (= 1.2.2)
kaminari-actionview (1.2.2)
actionview
kaminari-core (= 1.2.2)
kaminari-activerecord (1.2.2)
activerecord
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
language_server-protocol (3.17.0.4)
lint_roller (1.1.0)
logger (1.6.6)
Expand Down Expand Up @@ -189,12 +241,20 @@ GEM
racc (~> 1.4)
nokogiri (1.18.2-x86_64-linux-musl)
racc (~> 1.4)
openapi_first (2.3.0)
hana (~> 1.3)
json_schemer (>= 2.1, < 3.0)
openapi_parameters (>= 0.3.3, < 2.0)
rack (>= 2.2, < 4.0)
openapi_parameters (0.4.0)
rack (>= 2.2)
ostruct (0.6.1)
parallel (1.26.3)
parser (3.3.7.1)
ast (~> 2.4.1)
racc
pg (1.5.9)
pg (1.5.9)
pp (0.6.2)
prettyprint
prettyprint (0.2.0)
Expand All @@ -205,6 +265,8 @@ GEM
nio4r (~> 2.0)
racc (1.8.1)
rack (3.1.10)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-session (2.1.0)
Expand Down Expand Up @@ -245,6 +307,10 @@ GEM
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.2.1)
ransack (4.3.0)
activerecord (>= 6.1.5)
activesupport (>= 6.1.5)
i18n
ransack (4.3.0)
activerecord (>= 6.1.5)
activesupport (>= 6.1.5)
Expand Down Expand Up @@ -276,6 +342,9 @@ GEM
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-support (3.13.2)
rswag-ui (2.16.0)
actionpack (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1)
rubocop (1.72.0)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
Expand Down Expand Up @@ -307,6 +376,7 @@ GEM
rubocop-rails
ruby-progressbar (1.13.0)
securerandom (0.4.1)
simpleidn (0.2.3)
stringio (3.1.3)
thor (1.3.2)
timeout (0.4.3)
Expand Down Expand Up @@ -336,28 +406,38 @@ PLATFORMS
x86_64-linux-musl

DEPENDENCIES
activerecord-postgis-adapter
activerecord-postgis-adapter
bootsnap
brakeman
debug
dry-struct
dry-types
dry-struct
dry-types
factory_bot_rails
faker
jsonapi-serializer
kaminari
openapi_first
ostruct
pg
puma (>= 5.0)
rack-attack
rack-attack
rails (~> 8.0.1)
ransack
ransack
rspec-json_expectations
rspec-rails
rswag-ui
rubocop-rails-omakase

RUBY VERSION
ruby 3.4.1p0

RUBY VERSION
ruby 3.4.1p0

BUNDLED WITH
2.6.2
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
install:
docker compose build
docker compose run --rm app sh -c "cd openapi && npm install"
make run-app rake db:prepare
make run-app rake db:test:prepare

Expand Down
79 changes: 66 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,77 @@
# README
# Partners API

This README would normally document whatever steps are necessary to get the
application up and running.
The Partners API is a public Rails API application that allows users to search for partners based on location and flooring materials.

Things you may want to cover:
## What Is Special About Me

- Ruby version
- The service follows API first approach based on openapi.yaml spec.
- Swagger UI based on Open API service specification.
- Open API specification autogenerated using [TypeSpec](https://typespec.io/).
- Auto validation of the request input provided by the [openapi_first](https://github.com/ahx/openapi_first) middleware.
- The service follows [json:api](https://jsonapi.org/) message specification.
- [Ransack](https://github.com/activerecord-hackery/ransack) is a search library.
- [Kaminari](https://github.com/kaminari/kaminari) is for pagination.
- Development is fully containerized with Docker.
- Makefile has the most common commands to run the project.
- Test execution in GitHub CI

- System dependencies
Searching for partners by location is optimized using a precomputed and indexed _operating_area_ column in PostGIS, which allows efficient spatial queries.

- Configuration
## How Run Locally

- Database creation
### Docker

- Database initialization
The Partners API is a containerized application so you can run it using Docker. Install Docker using one of the ways from official site: https://docs.docker.com

- How to run the test suite
### Application

- Services (job queues, cache servers, search engines, etc.)
The Partners API project includes a Makefile that simplifies executing common development tasks. To start the project, run:

- Deployment instructions
```bash
make install
make start
```

- ...
Run rails console:

```bash
make console
```

Open application container:

```bash
make shell
```

Update openapi.yml from main.tsp:

```bash
make openapi-compile
```

## How Test Locally

Run rspec tests:

```bash
make test
```

There are 8 _Partner_ records created during _make install_ phase. You can open swagger on `http://localhost:3000/api-docs` and make a search call with the following request body:

```json
{
"filter": {
"flooring_materials": [1, 2],
"location": {
"latitude": 52.5163,
"longitude": 13.3777
}
},
"page": {
"number": 2,
"size": 2
}
}
```
19 changes: 19 additions & 0 deletions app/middleware/skip_middleware_by_path.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class SkipMiddlewareByPath
def initialize(app, middleware_class, options = {})
@app = app
@middleware_class = middleware_class
@middleware_options = options.except(:except)
@except_regexes = Array(options[:except])
end

def call(env)
request = Rack::Request.new(env)

if @except_regexes.any? { |regex| request.path.match?(regex) }
return @app.call(env)
end

middleware_instance = @middleware_class.new(@app, **@middleware_options)
middleware_instance.call(env)
end
end
7 changes: 7 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require_relative "boot"

require "rails/all"
require "openapi_first"
require_relative "../app/middleware/skip_middleware_by_path"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Expand Down Expand Up @@ -28,5 +30,10 @@ class Application < Rails::Application
# Middleware like session, flash, cookies can be added back manually.
# Skip views, helpers and assets when generating a new resource.
config.api_only = true

config.middleware.use SkipMiddlewareByPath, OpenapiFirst::Middlewares::RequestValidation,
spec: "public/openapi.yaml",
error_response: :jsonapi,
except: [ %r{^/api-docs}, %r{^/$} ]
end
end
3 changes: 3 additions & 0 deletions config/initializers/rswag_ui.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Rswag::Ui.configure do |c|
c.openapi_endpoint "/openapi.yaml", "API Documentation"
end
5 changes: 5 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
Rails.application.routes.draw do
if Rails.env.development?
mount Rswag::Ui::Engine => "/api-docs"
root to: redirect("/api-docs")
end

resources :partners, only: %i[show] do
collection do
post :search
Expand Down
1 change: 0 additions & 1 deletion db/migrate/20250215114412_create_partners_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ def change
t.timestamps
end

# TODO: add id and partner
create_table :flooring_materials_partners, id: false do |t|
t.references :partner, null: false, foreign_key: true
t.references :flooring_material, null: false, foreign_key: true
Expand Down
Loading