Its been a while
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
JWT_SECRET_KEY=your_super_secret_key_here
|
|
||||||
+3
-3
@@ -9,7 +9,7 @@ ARG RUBY_VERSION=3.3.1
|
|||||||
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
|
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
|
||||||
|
|
||||||
# Rails app lives here
|
# Rails app lives here
|
||||||
WORKDIR /rails
|
WORKDIR app
|
||||||
|
|
||||||
# Install base packages
|
# Install base packages
|
||||||
RUN apt-get update -qq && \
|
RUN apt-get update -qq && \
|
||||||
@@ -50,7 +50,7 @@ FROM base
|
|||||||
|
|
||||||
# Copy built artifacts: gems, application
|
# Copy built artifacts: gems, application
|
||||||
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
|
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
|
||||||
COPY --from=build /rails /rails
|
COPY --from=build /app /app
|
||||||
|
|
||||||
# Run and own only the runtime files as a non-root user for security
|
# Run and own only the runtime files as a non-root user for security
|
||||||
RUN groupadd --system --gid 1000 rails && \
|
RUN groupadd --system --gid 1000 rails && \
|
||||||
@@ -59,7 +59,7 @@ RUN groupadd --system --gid 1000 rails && \
|
|||||||
USER 1000:1000
|
USER 1000:1000
|
||||||
|
|
||||||
# Entrypoint prepares the database.
|
# Entrypoint prepares the database.
|
||||||
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
|
ENTRYPOINT ["/app/bin/docker-entrypoint"]
|
||||||
|
|
||||||
# Start the server by default, this can be overwritten at runtime
|
# Start the server by default, this can be overwritten at runtime
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ source "https://rubygems.org"
|
|||||||
ruby "3.3.1"
|
ruby "3.3.1"
|
||||||
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
|
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
|
||||||
gem "rails", "~> 7.2.2", ">= 7.2.2.1"
|
gem "rails", "~> 7.2.2", ">= 7.2.2.1"
|
||||||
# Use postgresql as the database for Active Record
|
|
||||||
gem "pg", "~> 1.1"
|
|
||||||
# Use the Puma web server [https://github.com/puma/puma]
|
# Use the Puma web server [https://github.com/puma/puma]
|
||||||
gem "puma", ">= 5.0"
|
gem "puma", ">= 5.0"
|
||||||
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
|
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
|
||||||
@@ -45,7 +43,8 @@ end
|
|||||||
|
|
||||||
gem "devise", "~> 4.9"
|
gem "devise", "~> 4.9"
|
||||||
|
|
||||||
gem "jwt", "~> 2.10"
|
# gem "jwt", "~> 2.10"
|
||||||
|
gem 'devise-jwt'
|
||||||
|
|
||||||
gem "dotenv-rails", groups: [ :development, :test ]
|
gem "dotenv-rails", groups: [ :development, :test ]
|
||||||
|
|
||||||
@@ -54,3 +53,11 @@ gem "rspec-rails", "~> 8.0"
|
|||||||
gem "rswag", "~> 2.16"
|
gem "rswag", "~> 2.16"
|
||||||
|
|
||||||
gem "rack-attack", "~> 6.7"
|
gem "rack-attack", "~> 6.7"
|
||||||
|
|
||||||
|
gem 'activerecord-sqlserver-adapter'
|
||||||
|
|
||||||
|
gem 'tiny_tds'
|
||||||
|
|
||||||
|
gem 'pundit'
|
||||||
|
|
||||||
|
gem 'httparty'
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
source "https://rubygems.org"
|
||||||
|
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
||||||
|
|
||||||
|
ruby "3.4.8"
|
||||||
|
|
||||||
|
# Bundle edge Rails instead:
|
||||||
|
# gem "rails", github: "rails/rails", branch: "7-2-stable"
|
||||||
|
gem "rails", "~> 7.2"
|
||||||
|
|
||||||
|
# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
|
||||||
|
gem "sprockets-rails"
|
||||||
|
|
||||||
|
# Use the Puma web server [https://github.com/puma/puma]
|
||||||
|
gem "puma", "~> 6.5"
|
||||||
|
|
||||||
|
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
|
||||||
|
gem "importmap-rails"
|
||||||
|
|
||||||
|
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
|
||||||
|
gem "turbo-rails"
|
||||||
|
|
||||||
|
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
|
||||||
|
gem "stimulus-rails"
|
||||||
|
|
||||||
|
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
|
||||||
|
gem "jbuilder"
|
||||||
|
|
||||||
|
# Use Redis adapter to run Action Cable in production
|
||||||
|
# gem "redis", "~> 5.3"
|
||||||
|
|
||||||
|
# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
|
||||||
|
# gem "kredis"
|
||||||
|
|
||||||
|
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
|
||||||
|
# gem "bcrypt", "~> 3.1.7"
|
||||||
|
|
||||||
|
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||||
|
gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ]
|
||||||
|
|
||||||
|
# Reduces boot times through caching; required in config/boot.rb
|
||||||
|
gem "bootsnap", require: false
|
||||||
|
|
||||||
|
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
|
||||||
|
# gem "image_processing", "~> 1.2"
|
||||||
|
|
||||||
|
group :development, :test do
|
||||||
|
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
|
||||||
|
# gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"
|
||||||
|
|
||||||
|
# Static analysis for security vulnerabilities [https://brakemanscanner.org/]
|
||||||
|
gem "brakeman", require: false
|
||||||
|
|
||||||
|
# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
|
||||||
|
gem "rubocop-rails-omakase", require: false
|
||||||
|
|
||||||
|
gem "bundler-audit"
|
||||||
|
gem "rspec-rails"
|
||||||
|
gem "rubocop-rails"
|
||||||
|
gem "rubocop-rspec"
|
||||||
|
end
|
||||||
|
|
||||||
|
group :development do
|
||||||
|
# Use console on exceptions pages [https://github.com/rails/web-console]
|
||||||
|
gem "web-console"
|
||||||
|
|
||||||
|
# Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler]
|
||||||
|
gem "rack-mini-profiler"
|
||||||
|
|
||||||
|
gem "pry-rails"
|
||||||
|
end
|
||||||
|
|
||||||
|
group :test do
|
||||||
|
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
|
||||||
|
gem "capybara"
|
||||||
|
gem "selenium-webdriver"
|
||||||
|
end
|
||||||
|
|
||||||
|
gem 'activerecord-sqlserver-adapter'
|
||||||
|
gem 'tiny_tds'
|
||||||
|
gem 'devise'
|
||||||
|
gem 'pundit'
|
||||||
|
gem "tailwindcss-rails"
|
||||||
|
gem 'docx'
|
||||||
|
gem 'httparty'
|
||||||
|
gem 'combine_pdf'
|
||||||
|
gem 'pdf-reader'
|
||||||
|
gem 'rails_icons'
|
||||||
|
gem 'fastimage'
|
||||||
|
gem 'rubyzip', require: 'zip'
|
||||||
|
# gem "solid_queue"
|
||||||
|
gem 'delayed_job_active_record'
|
||||||
|
gem 'daemons'
|
||||||
|
gem 'image_processing'
|
||||||
|
gem "ruby-vips"
|
||||||
|
gem 'whenever', require: false
|
||||||
|
gem 'amatch'
|
||||||
|
gem 'figaro'
|
||||||
+46
-4
@@ -54,6 +54,9 @@ GEM
|
|||||||
activemodel (= 7.2.2.1)
|
activemodel (= 7.2.2.1)
|
||||||
activesupport (= 7.2.2.1)
|
activesupport (= 7.2.2.1)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
|
activerecord-sqlserver-adapter (7.2.9)
|
||||||
|
activerecord (~> 7.2.0)
|
||||||
|
tiny_tds
|
||||||
activestorage (7.2.2.1)
|
activestorage (7.2.2.1)
|
||||||
actionpack (= 7.2.2.1)
|
actionpack (= 7.2.2.1)
|
||||||
activejob (= 7.2.2.1)
|
activejob (= 7.2.2.1)
|
||||||
@@ -87,6 +90,7 @@ GEM
|
|||||||
concurrent-ruby (1.3.5)
|
concurrent-ruby (1.3.5)
|
||||||
connection_pool (2.5.3)
|
connection_pool (2.5.3)
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
|
csv (3.3.5)
|
||||||
date (3.4.1)
|
date (3.4.1)
|
||||||
debug (1.10.0)
|
debug (1.10.0)
|
||||||
irb (~> 1.10)
|
irb (~> 1.10)
|
||||||
@@ -97,16 +101,33 @@ GEM
|
|||||||
railties (>= 4.1.0)
|
railties (>= 4.1.0)
|
||||||
responders
|
responders
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
|
devise-jwt (0.13.0)
|
||||||
|
devise (>= 4.0.0, < 6.0.0)
|
||||||
|
warden-jwt_auth (~> 0.10)
|
||||||
diff-lcs (1.6.2)
|
diff-lcs (1.6.2)
|
||||||
dotenv (3.1.8)
|
dotenv (3.1.8)
|
||||||
dotenv-rails (3.1.8)
|
dotenv-rails (3.1.8)
|
||||||
dotenv (= 3.1.8)
|
dotenv (= 3.1.8)
|
||||||
railties (>= 6.1)
|
railties (>= 6.1)
|
||||||
drb (2.2.3)
|
drb (2.2.3)
|
||||||
|
dry-auto_inject (1.2.1)
|
||||||
|
dry-core (~> 1.1)
|
||||||
|
zeitwerk (~> 2.6)
|
||||||
|
dry-configurable (1.4.0)
|
||||||
|
dry-core (~> 1.0)
|
||||||
|
zeitwerk (~> 2.6)
|
||||||
|
dry-core (1.2.0)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
logger
|
||||||
|
zeitwerk (~> 2.6)
|
||||||
erb (5.0.1)
|
erb (5.0.1)
|
||||||
erubi (1.13.1)
|
erubi (1.13.1)
|
||||||
globalid (1.2.1)
|
globalid (1.2.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
|
httparty (0.24.2)
|
||||||
|
csv
|
||||||
|
mini_mime (>= 1.0.0)
|
||||||
|
multi_xml (>= 0.5.2)
|
||||||
i18n (1.14.7)
|
i18n (1.14.7)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
io-console (0.8.0)
|
io-console (0.8.0)
|
||||||
@@ -118,7 +139,7 @@ GEM
|
|||||||
json-schema (5.1.1)
|
json-schema (5.1.1)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
bigdecimal (~> 3.1)
|
bigdecimal (~> 3.1)
|
||||||
jwt (2.10.1)
|
jwt (3.2.0)
|
||||||
base64
|
base64
|
||||||
language_server-protocol (3.17.0.5)
|
language_server-protocol (3.17.0.5)
|
||||||
lint_roller (1.1.0)
|
lint_roller (1.1.0)
|
||||||
@@ -135,6 +156,8 @@ GEM
|
|||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
minitest (5.25.5)
|
minitest (5.25.5)
|
||||||
msgpack (1.8.0)
|
msgpack (1.8.0)
|
||||||
|
multi_xml (0.9.1)
|
||||||
|
bigdecimal (>= 3.1, < 5)
|
||||||
net-imap (0.5.8)
|
net-imap (0.5.8)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
@@ -166,7 +189,6 @@ GEM
|
|||||||
parser (3.3.8.0)
|
parser (3.3.8.0)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
pg (1.5.9)
|
|
||||||
pp (0.6.2)
|
pp (0.6.2)
|
||||||
prettyprint
|
prettyprint
|
||||||
prettyprint (0.2.0)
|
prettyprint (0.2.0)
|
||||||
@@ -177,6 +199,8 @@ GEM
|
|||||||
public_suffix (6.0.2)
|
public_suffix (6.0.2)
|
||||||
puma (6.6.0)
|
puma (6.6.0)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
|
pundit (2.5.2)
|
||||||
|
activesupport (>= 3.0.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (3.1.16)
|
rack (3.1.16)
|
||||||
rack-attack (6.7.0)
|
rack-attack (6.7.0)
|
||||||
@@ -296,6 +320,16 @@ GEM
|
|||||||
stringio (3.1.7)
|
stringio (3.1.7)
|
||||||
thor (1.3.2)
|
thor (1.3.2)
|
||||||
timeout (0.4.3)
|
timeout (0.4.3)
|
||||||
|
tiny_tds (3.4.0)
|
||||||
|
bigdecimal (>= 2.0.0)
|
||||||
|
tiny_tds (3.4.0-aarch64-linux-gnu)
|
||||||
|
bigdecimal (>= 2.0.0)
|
||||||
|
tiny_tds (3.4.0-aarch64-linux-musl)
|
||||||
|
bigdecimal (>= 2.0.0)
|
||||||
|
tiny_tds (3.4.0-x86_64-linux-gnu)
|
||||||
|
bigdecimal (>= 2.0.0)
|
||||||
|
tiny_tds (3.4.0-x86_64-linux-musl)
|
||||||
|
bigdecimal (>= 2.0.0)
|
||||||
tzinfo (2.0.6)
|
tzinfo (2.0.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
unicode-display_width (3.1.4)
|
unicode-display_width (3.1.4)
|
||||||
@@ -304,6 +338,11 @@ GEM
|
|||||||
useragent (0.16.11)
|
useragent (0.16.11)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
|
warden-jwt_auth (0.12.0)
|
||||||
|
dry-auto_inject (>= 0.8, < 2)
|
||||||
|
dry-configurable (>= 0.13, < 2)
|
||||||
|
jwt (>= 2.1, < 4)
|
||||||
|
warden (~> 1.2)
|
||||||
websocket-driver (0.8.0)
|
websocket-driver (0.8.0)
|
||||||
base64
|
base64
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
@@ -323,20 +362,23 @@ PLATFORMS
|
|||||||
x86_64-linux-musl
|
x86_64-linux-musl
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
activerecord-sqlserver-adapter
|
||||||
bootsnap
|
bootsnap
|
||||||
brakeman
|
brakeman
|
||||||
debug
|
debug
|
||||||
devise (~> 4.9)
|
devise (~> 4.9)
|
||||||
|
devise-jwt
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
jwt (~> 2.10)
|
httparty
|
||||||
pg (~> 1.1)
|
|
||||||
puma (>= 5.0)
|
puma (>= 5.0)
|
||||||
|
pundit
|
||||||
rack-attack (~> 6.7)
|
rack-attack (~> 6.7)
|
||||||
rack-cors
|
rack-cors
|
||||||
rails (~> 7.2.2, >= 7.2.2.1)
|
rails (~> 7.2.2, >= 7.2.2.1)
|
||||||
rspec-rails (~> 8.0)
|
rspec-rails (~> 8.0)
|
||||||
rswag (~> 2.16)
|
rswag (~> 2.16)
|
||||||
rubocop-rails-omakase
|
rubocop-rails-omakase
|
||||||
|
tiny_tds
|
||||||
tzinfo-data
|
tzinfo-data
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
app: bin/rails server -b 0.0.0.0 -p 3005
|
||||||
@@ -115,7 +115,7 @@ POST /api/v1/logout
|
|||||||
Authorization: Bearer <your_access_token>
|
Authorization: Bearer <your_access_token>
|
||||||
```
|
```
|
||||||
|
|
||||||
blacklists the current token (real logout, token becomes invalid)
|
retires the current token (real logout, token becomes invalid)
|
||||||
|
|
||||||
## 👥 role-based authorization
|
## 👥 role-based authorization
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ end
|
|||||||
## 🔒 security features
|
## 🔒 security features
|
||||||
|
|
||||||
* **no secret fallbacks**: JWT_SECRET_KEY must be set (crashes if missing)
|
* **no secret fallbacks**: JWT_SECRET_KEY must be set (crashes if missing)
|
||||||
* **token blacklisting**: logout actually invalidates tokens
|
* **token retireing**: logout actually invalidates tokens
|
||||||
* **refresh tokens**: short-lived access tokens (1 hour) + long-lived refresh tokens (7 days)
|
* **refresh tokens**: short-lived access tokens (1 hour) + long-lived refresh tokens (7 days)
|
||||||
* **rate limiting**: login, signup, and refresh endpoints are throttled
|
* **rate limiting**: login, signup, and refresh endpoints are throttled
|
||||||
* **JTI tracking**: every token has a unique identifier for precise control
|
* **JTI tracking**: every token has a unique identifier for precise control
|
||||||
@@ -176,8 +176,8 @@ rspec
|
|||||||
add these to your scheduled jobs (sidekiq, cron, etc):
|
add these to your scheduled jobs (sidekiq, cron, etc):
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
# clean up expired blacklisted tokens
|
# clean up expired retired tokens
|
||||||
BlacklistedToken.cleanup_expired
|
RetiredToken.cleanup_expired
|
||||||
|
|
||||||
# clean up old refresh tokens
|
# clean up old refresh tokens
|
||||||
RefreshToken.cleanup_old_tokens
|
RefreshToken.cleanup_old_tokens
|
||||||
@@ -196,8 +196,8 @@ REDIS_URL=your_redis_url (optional, for rack-attack)
|
|||||||
### database indexes
|
### database indexes
|
||||||
|
|
||||||
migrations include proper indexes for performance:
|
migrations include proper indexes for performance:
|
||||||
* `blacklisted_tokens.jti` (unique)
|
* `retired_tokens.jti` (unique)
|
||||||
* `blacklisted_tokens.exp`
|
* `retired_tokens.exp`
|
||||||
* `refresh_tokens.token` (unique)
|
* `refresh_tokens.token` (unique)
|
||||||
* `refresh_tokens.user_id + revoked`
|
* `refresh_tokens.user_id + revoked`
|
||||||
* `users.role`
|
* `users.role`
|
||||||
@@ -210,7 +210,7 @@ open issues or pull requests.
|
|||||||
## ✨ features
|
## ✨ features
|
||||||
|
|
||||||
* ✅ JWT authentication with secure token generation (includes JTI for tracking)
|
* ✅ JWT authentication with secure token generation (includes JTI for tracking)
|
||||||
* ✅ Token blacklisting for real logout (tokens are invalidated on logout)
|
* ✅ Token retireing for real logout (tokens are invalidated on logout)
|
||||||
* ✅ Refresh tokens (7-day expiry, keeps users logged in securely)
|
* ✅ Refresh tokens (7-day expiry, keeps users logged in securely)
|
||||||
* ✅ Role-based authorization (user, moderator, admin roles)
|
* ✅ Role-based authorization (user, moderator, admin roles)
|
||||||
* ✅ Rate limiting with Rack::Attack (prevents brute force attacks)
|
* ✅ Rate limiting with Rack::Attack (prevents brute force attacks)
|
||||||
@@ -232,7 +232,7 @@ feel free to fork, star, share, or improve.
|
|||||||
|
|
||||||
## ⚠️ disclaimer
|
## ⚠️ disclaimer
|
||||||
|
|
||||||
this template includes production-grade features like token blacklisting, refresh tokens, and role-based auth.
|
this template includes production-grade features like token retireing, refresh tokens, and role-based auth.
|
||||||
however, you should still:
|
however, you should still:
|
||||||
* review security settings for your specific use case
|
* review security settings for your specific use case
|
||||||
* set up proper monitoring and logging
|
* set up proper monitoring and logging
|
||||||
|
|||||||
@@ -42,15 +42,15 @@ module Api
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# post /logout -> NOW with real token blacklisting
|
# post /logout -> NOW with real token retireing
|
||||||
# adds the current token to blacklist so it can't be used again
|
# adds the current token to retire so it can't be used again
|
||||||
# authorize_request ensures @current_user and token are present
|
# authorize_request ensures @current_user and token are present
|
||||||
def logout
|
def logout
|
||||||
header = request.headers["Authorization"]
|
header = request.headers["Authorization"]
|
||||||
token = header.split(" ").last if header
|
token = header.split(" ").last if header
|
||||||
|
|
||||||
decoded = JsonWebToken.decode(token)
|
decoded = JsonWebToken.decode(token)
|
||||||
BlacklistedToken.create!(
|
RetiredToken.create!(
|
||||||
jti: decoded[:jti],
|
jti: decoded[:jti],
|
||||||
user_id: decoded[:user_id],
|
user_id: decoded[:user_id],
|
||||||
exp: Time.at(decoded[:exp])
|
exp: Time.at(decoded[:exp])
|
||||||
@@ -59,7 +59,7 @@ module Api
|
|||||||
# also revoke all refresh tokens for this user
|
# also revoke all refresh tokens for this user
|
||||||
@current_user.refresh_tokens.update_all(revoked: true)
|
@current_user.refresh_tokens.update_all(revoked: true)
|
||||||
|
|
||||||
render json: { message: "Successfully logged out. Token blacklisted." }, status: :ok
|
render json: { message: "Successfully logged out. Token retired." }, status: :ok
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
render json: { error: "Logout failed: #{e.message}" }, status: :unprocessable_entity
|
render json: { error: "Logout failed: #{e.message}" }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class BrokersController < ApplicationController
|
||||||
|
# before_action :authenticate_user!
|
||||||
|
|
||||||
|
def employers_list
|
||||||
|
broker_id = params[:id]
|
||||||
|
broker = Baclight::Broker.find(broker_id)
|
||||||
|
employers = broker.baclight_employers.order(:name).map { |emp| {name: emp.name, entity_key: emp.pl_plan_key }}
|
||||||
|
|
||||||
|
render json: employers, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class CarriersController < ApplicationController
|
||||||
|
before_action :authenticate_user!
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class ClaimsController < ApplicationController
|
||||||
|
|
||||||
|
def member_claims
|
||||||
|
pb_entity_key = params[:id]
|
||||||
|
member_claims = MemberClaimsService.new(pb_entity_key).call
|
||||||
|
|
||||||
|
if member_claims.present?
|
||||||
|
render json: member_claims, status: :ok
|
||||||
|
else
|
||||||
|
render json: { error: "Member Claims not found" }, status: :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class EmployersController < ApplicationController
|
||||||
|
# before_action :authenticate_user!
|
||||||
|
|
||||||
|
def id_cards
|
||||||
|
pl_plan_key = params[:id]
|
||||||
|
id_cards = EmployersService::IdCards.new(pl_plan_key).call
|
||||||
|
|
||||||
|
if id_cards.code == 200
|
||||||
|
content_disposition = id_cards.headers['Content-Disposition']
|
||||||
|
filename = content_disposition[/filename="?([^"]*)"?/, 1]
|
||||||
|
# filename = params[:filename].presence || 'employer_idcards.zip'
|
||||||
|
|
||||||
|
send_data(
|
||||||
|
id_cards.body,
|
||||||
|
type: 'application/zip',
|
||||||
|
filename: filename,
|
||||||
|
disposition: 'attachment'
|
||||||
|
)
|
||||||
|
else
|
||||||
|
render json: { error: "Employer not found" }, status: :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def members_list
|
||||||
|
pl_plan_key = params[:id]
|
||||||
|
employer = Baclight::Employer.find_by(pl_plan_key: pl_plan_key)
|
||||||
|
members = employer.baclight_members.active.order(:name).map { |mem| {name: mem.name, entity_key: mem.pb_entity_key}}
|
||||||
|
|
||||||
|
render json: members, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class IdCardsController < ApplicationController
|
||||||
|
|
||||||
|
def member_card
|
||||||
|
pb_entity_key = params[:id]
|
||||||
|
layout = params[:layout]
|
||||||
|
|
||||||
|
@member = Baclight::Member.find_by(pb_entity_key: pb_entity_key)
|
||||||
|
# authorize :id_cards, @member
|
||||||
|
|
||||||
|
url_components = {
|
||||||
|
host: ENV["BACLIGHT_SERVER_HOST"],
|
||||||
|
port: ENV["BACLIGHT_SERVER_PORT"],
|
||||||
|
path: "/api/v1/web_id_cards/member_card/#{pb_entity_key}/#{layout}"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = HTTParty.get(
|
||||||
|
URI::HTTP.build(url_components),
|
||||||
|
headers: { 'Content-Type' => 'application/json', 'Accept' => 'application/pdf' },
|
||||||
|
stream_body: true
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.code == 200
|
||||||
|
content_disposition = response.headers['Content-Disposition']
|
||||||
|
filename = content_disposition[/filename="?([^"]*)"?/, 1]
|
||||||
|
disposition = layout == "MobileDisplayCard" ? 'inline' : 'attachment'
|
||||||
|
|
||||||
|
send_data(
|
||||||
|
response.body,
|
||||||
|
type: 'application/pdf',
|
||||||
|
filename: filename,
|
||||||
|
disposition: disposition
|
||||||
|
)
|
||||||
|
else
|
||||||
|
render json: { error: "Member not found" }, status: :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def member_benefits
|
||||||
|
pb_entity_key = params[:id]
|
||||||
|
|
||||||
|
@member = Baclight::Member.find_by(pb_entity_key: pb_entity_key)
|
||||||
|
plan_id = @member.id_card_plan_id
|
||||||
|
|
||||||
|
plan_benefits = Baclight::IdCardPlan.joins(:baclight_id_card_plan_benefits)
|
||||||
|
.select('id_card_plans.id, id_card_plans.title, id_card_plan_benefits.id, id_card_plan_benefits.sequence, id_card_plan_benefits.benefit, id_card_plan_benefits.benefit_desc')
|
||||||
|
.where(id: plan_id)
|
||||||
|
.distinct
|
||||||
|
|
||||||
|
if plan_benefits.present?
|
||||||
|
render json: plan_benefits, status: :ok
|
||||||
|
else
|
||||||
|
render json: { error: "Plan Benefits not found" }, status: :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def employer_cards
|
||||||
|
pl_plan_key = params[:id]
|
||||||
|
|
||||||
|
@employer = Baclight::Employer.find_by(pl_plan_key: pl_plan_key)
|
||||||
|
|
||||||
|
# authorize :id_cards, @employer
|
||||||
|
|
||||||
|
url_components = {
|
||||||
|
host: ENV["BACLIGHT_SERVER_HOST"],
|
||||||
|
port: ENV["BACLIGHT_SERVER_PORT"],
|
||||||
|
path: "/api/v1/web_id_cards/employer_cards/#{pl_plan_key}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Send POST with params and enable streaming
|
||||||
|
response = HTTParty.get(
|
||||||
|
URI::HTTP.build(url_components),
|
||||||
|
headers: { 'Content-Type' => 'application/json', 'Accept' => 'application/zip' },
|
||||||
|
stream_body: true
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.code == 200
|
||||||
|
content_disposition = response.headers['Content-Disposition']
|
||||||
|
filename = content_disposition[/filename="?([^"]*)"?/, 1]
|
||||||
|
# filename = params[:filename].presence || 'employer_idcards.zip'
|
||||||
|
|
||||||
|
send_data(
|
||||||
|
response.body,
|
||||||
|
type: 'application/zip',
|
||||||
|
filename: filename,
|
||||||
|
disposition: 'attachment'
|
||||||
|
)
|
||||||
|
else
|
||||||
|
render json: { error: "Employer not found" }, status: :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class MembersController < ApplicationController
|
||||||
|
# before_action :authenticate_user!
|
||||||
|
|
||||||
|
def initialize_dashboard
|
||||||
|
pb_entity_key = params[:id]
|
||||||
|
@member = Baclight::Member.find_by(pb_entity_key: pb_entity_key)
|
||||||
|
authorize @member
|
||||||
|
|
||||||
|
dashboard_data = MembersService::InitializeDashboard.new(@member).call
|
||||||
|
|
||||||
|
render json: dashboard_data, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def id_card
|
||||||
|
pb_entity_key = params[:id]
|
||||||
|
layout = params[:layout]
|
||||||
|
@member = Baclight::Member.find_by(pb_entity_key: pb_entity_key)
|
||||||
|
# authorize @member
|
||||||
|
|
||||||
|
generated_id_card = MembersService::IdCard.new(@member, layout).call
|
||||||
|
|
||||||
|
if generated_id_card.code == 200
|
||||||
|
content_disposition = generated_id_card.headers['Content-Disposition']
|
||||||
|
filename = content_disposition[/filename="?([^"]*)"?/, 1]
|
||||||
|
# filename = params[:filename].presence || 'employer_idcards.zip'
|
||||||
|
|
||||||
|
send_data(
|
||||||
|
generated_id_card.body,
|
||||||
|
type: 'application/pdf',
|
||||||
|
filename: filename,
|
||||||
|
disposition: 'inline'
|
||||||
|
)
|
||||||
|
else
|
||||||
|
render json: { error: "Member not found" }, status: :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def network_provider
|
||||||
|
pl_plan_key = params[:id]
|
||||||
|
|
||||||
|
network_provider = Baclight::IdCardSetup.find_by(pl_plan_key: pl_plan_key).slice(:network_provider)
|
||||||
|
|
||||||
|
if network_provider.present?
|
||||||
|
render json: network_provider, status: :ok
|
||||||
|
else
|
||||||
|
render json: { error: "Network Provider not found" }, status: :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def plan_benefits
|
||||||
|
pb_entity_key = params[:id]
|
||||||
|
plan_benefits = MembersService::PlanBenefits.new(pb_entity_key).call
|
||||||
|
|
||||||
|
if plan_benefits.present?
|
||||||
|
render json: plan_benefits, status: :ok
|
||||||
|
else
|
||||||
|
render json: { error: "Plan Benefits not found" }, status: :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def claims
|
||||||
|
pb_entity_key = params[:id]
|
||||||
|
@member = Baclight::Member.find_by(pb_entity_key: pb_entity_key)
|
||||||
|
|
||||||
|
recent_claims = MembersService::Claims.new(@member).call
|
||||||
|
|
||||||
|
render json: recent_claims, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class NetworkProviderController < ApplicationController
|
||||||
|
|
||||||
|
def employer_network_provider
|
||||||
|
pl_plan_key = params[:id]
|
||||||
|
|
||||||
|
network_provider = Baclight::IdCardSetup.find_by(pl_plan_key: pl_plan_key).slice(:network_provider)
|
||||||
|
|
||||||
|
if network_provider.present?
|
||||||
|
render json: network_provider, status: :ok
|
||||||
|
else
|
||||||
|
render json: { error: "Network Provider not found" }, status: :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
module Api
|
||||||
|
module V1
|
||||||
|
class ProvidersController < ApplicationController
|
||||||
|
before_action :authenticate_user!
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
class ApplicationController < ActionController::API
|
class ApplicationController < ActionController::API
|
||||||
|
include Pundit::Authorization
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ module AuthorizeRequest
|
|||||||
begin
|
begin
|
||||||
decoded = JsonWebToken.decode(token)
|
decoded = JsonWebToken.decode(token)
|
||||||
|
|
||||||
# check if token is blacklisted (logged out)
|
# check if token is retired (logged out)
|
||||||
if BlacklistedToken.blacklisted?(decoded[:jti])
|
if RetiredToken.retired?(decoded[:jti])
|
||||||
render json: { error: "Token has been revoked" }, status: :unauthorized
|
render json: { error: "Token has been revoked" }, status: :unauthorized
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Users::ConfirmationsController < Devise::ConfirmationsController
|
||||||
|
# GET /resource/confirmation/new
|
||||||
|
# def new
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# POST /resource/confirmation
|
||||||
|
# def create
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# GET /resource/confirmation?confirmation_token=abcdef
|
||||||
|
# def show
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# protected
|
||||||
|
|
||||||
|
# The path used after resending confirmation instructions.
|
||||||
|
# def after_resending_confirmation_instructions_path_for(resource_name)
|
||||||
|
# super(resource_name)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# The path used after confirmation.
|
||||||
|
# def after_confirmation_path_for(resource_name, resource)
|
||||||
|
# super(resource_name, resource)
|
||||||
|
# end
|
||||||
|
end
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||||
|
# You should configure your model like this:
|
||||||
|
# devise :omniauthable, omniauth_providers: [:twitter]
|
||||||
|
|
||||||
|
# You should also create an action method in this controller like this:
|
||||||
|
# def twitter
|
||||||
|
# end
|
||||||
|
|
||||||
|
# More info at:
|
||||||
|
# https://github.com/heartcombo/devise#omniauth
|
||||||
|
|
||||||
|
# GET|POST /resource/auth/twitter
|
||||||
|
# def passthru
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# GET|POST /users/auth/twitter/callback
|
||||||
|
# def failure
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# protected
|
||||||
|
|
||||||
|
# The path used when OmniAuth fails
|
||||||
|
# def after_omniauth_failure_path_for(scope)
|
||||||
|
# super(scope)
|
||||||
|
# end
|
||||||
|
end
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Users::PasswordsController < Devise::PasswordsController
|
||||||
|
# GET /resource/password/new
|
||||||
|
# def new
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# POST /resource/password
|
||||||
|
# def create
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# GET /resource/password/edit?reset_password_token=abcdef
|
||||||
|
# def edit
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# PUT /resource/password
|
||||||
|
# def update
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# protected
|
||||||
|
|
||||||
|
# def after_resetting_password_path_for(resource)
|
||||||
|
# super(resource)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# The path used after sending reset password instructions
|
||||||
|
# def after_sending_reset_password_instructions_path_for(resource_name)
|
||||||
|
# super(resource_name)
|
||||||
|
# end
|
||||||
|
end
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Users::RegistrationsController < Devise::RegistrationsController
|
||||||
|
respond_to :json
|
||||||
|
before_action :configure_sign_up_params, only: [:create]
|
||||||
|
# before_action :configure_account_update_params, only: [:update]
|
||||||
|
|
||||||
|
|
||||||
|
# GET /resource/sign_up
|
||||||
|
# def new
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# POST /resource
|
||||||
|
# def create
|
||||||
|
# super do |resource|
|
||||||
|
# resource.sync_role_entity!
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
# GET /resource/edit
|
||||||
|
# def edit
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# PUT /resource
|
||||||
|
# def update
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# DELETE /resource
|
||||||
|
# def destroy
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# GET /resource/cancel
|
||||||
|
# Forces the session data which is usually expired after sign
|
||||||
|
# in to be expired now. This is useful if the user wants to
|
||||||
|
# cancel oauth signing in/up in the middle of the process,
|
||||||
|
# removing all OAuth session data.
|
||||||
|
# def cancel
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# protected
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# def respond_with(resource, _opts = {})
|
||||||
|
# if resource.persisted?
|
||||||
|
# render json: {
|
||||||
|
# status: { code: 200, message: 'Signed up successfully.' },
|
||||||
|
# data: resource
|
||||||
|
# }, status: :ok
|
||||||
|
# else
|
||||||
|
# render json: {
|
||||||
|
# status: { message: "User couldn't be created successfully. #{resource.errors.full_messages.to_sentence}" }
|
||||||
|
# }, status: :unprocessable_entity
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
|
||||||
|
def respond_with(resource, _opts = {})
|
||||||
|
if resource.persisted?
|
||||||
|
# Customize what is returned upon successful login here
|
||||||
|
render json: {
|
||||||
|
message: "Signed up successfully.",
|
||||||
|
user: {
|
||||||
|
id: resource.id,
|
||||||
|
email: resource.email,
|
||||||
|
name: resource.name,
|
||||||
|
role: resource.role,
|
||||||
|
role_id: resource.role_id,
|
||||||
|
jti: resource.jti,
|
||||||
|
keychain: resource.keychain,
|
||||||
|
created_at: resource.created_at,
|
||||||
|
updated_at: resource.updated_at
|
||||||
|
}
|
||||||
|
}, status: :ok
|
||||||
|
else
|
||||||
|
render json: {
|
||||||
|
status: { message: "User couldn't be created successfully. #{resource.errors.full_messages.to_sentence}" }
|
||||||
|
}, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# If you have extra params to permit, append them to the sanitizer.
|
||||||
|
def configure_sign_up_params
|
||||||
|
devise_parameter_sanitizer.permit(:sign_up, keys: [:role, :role_id, :date_of_birth, :last_four_ssn])
|
||||||
|
end
|
||||||
|
|
||||||
|
# If you have extra params to permit, append them to the sanitizer.
|
||||||
|
# def configure_account_update_params
|
||||||
|
# devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
|
||||||
|
# end
|
||||||
|
|
||||||
|
# The path used after sign up.
|
||||||
|
# def after_sign_up_path_for(resource)
|
||||||
|
# super(resource)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# The path used after sign up for inactive accounts.
|
||||||
|
# def after_inactive_sign_up_path_for(resource)
|
||||||
|
# super(resource)
|
||||||
|
# end
|
||||||
|
end
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Users::SessionsController < Devise::SessionsController
|
||||||
|
respond_to :json
|
||||||
|
# POST /resource/sign_in
|
||||||
|
# def create
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# DELETE /resource/sign_out
|
||||||
|
# def destroy
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def respond_with(resource, _opts = {})
|
||||||
|
puts
|
||||||
|
render json: {
|
||||||
|
status: { code: 200, message: 'Logged in successfully.' },
|
||||||
|
user: {
|
||||||
|
id: resource.id,
|
||||||
|
email: resource.email,
|
||||||
|
name: resource.name,
|
||||||
|
role: resource.role,
|
||||||
|
role_id: resource.role_id,
|
||||||
|
jti: resource.jti,
|
||||||
|
keychain: resource.keychain,
|
||||||
|
created_at: resource.created_at,
|
||||||
|
updated_at: resource.updated_at
|
||||||
|
}
|
||||||
|
}, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def respond_to_on_destroy
|
||||||
|
if current_user
|
||||||
|
render json: {
|
||||||
|
status: 200,
|
||||||
|
message: "Logged out successfully."
|
||||||
|
}, status: :ok
|
||||||
|
else
|
||||||
|
render json: {
|
||||||
|
status: 401,
|
||||||
|
message: "Couldn't find an active session."
|
||||||
|
}, status: :unauthorized
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Users::UnlocksController < Devise::UnlocksController
|
||||||
|
# GET /resource/unlock/new
|
||||||
|
# def new
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# POST /resource/unlock
|
||||||
|
# def create
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# GET /resource/unlock?unlock_token=abcdef
|
||||||
|
# def show
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
|
||||||
|
# protected
|
||||||
|
|
||||||
|
# The path used after sending unlock password instructions
|
||||||
|
# def after_sending_unlock_instructions_path_for(resource)
|
||||||
|
# super(resource)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# The path used after unlocking the resource
|
||||||
|
# def after_unlock_path_for(resource)
|
||||||
|
# super(resource)
|
||||||
|
# end
|
||||||
|
end
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
class ApplicationRecord < ActiveRecord::Base
|
class ApplicationRecord < ActiveRecord::Base
|
||||||
primary_abstract_class
|
primary_abstract_class
|
||||||
|
|
||||||
|
establish_connection :primary
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
module Baclight
|
||||||
|
class Broker < BaclightRecord
|
||||||
|
|
||||||
|
self.table_name = 'Brokers'
|
||||||
|
|
||||||
|
belongs_to :baclight_carrier, class_name: "Baclight::Carrier", foreign_key: "carrier_id"
|
||||||
|
has_many :baclight_employers, class_name: "Baclight::Employer", foreign_key: "broker_id", primary_key: "id"
|
||||||
|
has_many :baclight_members,
|
||||||
|
through: :baclight_employers,
|
||||||
|
source: :baclight_member
|
||||||
|
|
||||||
|
alias_attribute :id, :id
|
||||||
|
alias_attribute :name, :name
|
||||||
|
alias_attribute :carrier_id, :carrier_id
|
||||||
|
alias_attribute :created_at, :created_at
|
||||||
|
alias_attribute :updated_at, :updated_at
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
module Baclight
|
||||||
|
class Carrier < BaclightRecord
|
||||||
|
|
||||||
|
self.table_name = 'Carriers'
|
||||||
|
|
||||||
|
has_many :baclight_brokers, class_name: "Baclight::Broker", foreign_key: "carrier_id", primary_key: "id"
|
||||||
|
has_many :baclight_employers,
|
||||||
|
through: :baclight_brokers,
|
||||||
|
source: :baclight_employer
|
||||||
|
has_many :baclight_members,
|
||||||
|
through: :baclight_employers,
|
||||||
|
source: :baclight_member
|
||||||
|
|
||||||
|
alias_attribute :id, :id
|
||||||
|
alias_attribute :name, :name
|
||||||
|
alias_attribute :created_at, :created_at
|
||||||
|
alias_attribute :updated_at, :updated_at
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
module Baclight
|
||||||
|
class Employer < BaclightRecord
|
||||||
|
|
||||||
|
self.table_name = 'Employers'
|
||||||
|
|
||||||
|
belongs_to :baclight_broker, class_name: "Baclight::Broker", foreign_key: "broker_id"
|
||||||
|
has_many :baclight_members, class_name: "Baclight::Member", foreign_key: "employer_id", primary_key: "id"
|
||||||
|
|
||||||
|
alias_attribute :id, :id
|
||||||
|
alias_attribute :name, :name
|
||||||
|
alias_attribute :slug, :slug
|
||||||
|
alias_attribute :pl_plan_key, :pl_plan_key
|
||||||
|
alias_attribute :company_pb_entity_key, :company_pb_entity_key
|
||||||
|
alias_attribute :plan_id, :plan_id
|
||||||
|
alias_attribute :group_number, :group_number
|
||||||
|
alias_attribute :effective_date, :effective_date
|
||||||
|
alias_attribute :active, :active
|
||||||
|
alias_attribute :initialized, :initialized
|
||||||
|
alias_attribute :broker_id, :broker_id
|
||||||
|
alias_attribute :created_at, :created_at
|
||||||
|
alias_attribute :updated_at, :updated_at
|
||||||
|
|
||||||
|
def belongs_to_levels
|
||||||
|
self.joins(baclight_broker: :baclight_carrier).select(
|
||||||
|
'baclight_broker.id AS broker_id',
|
||||||
|
'baclight_carrier.id AS carrier_id'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
module Baclight
|
||||||
|
class IdCardPlan < BaclightRecord
|
||||||
|
|
||||||
|
self.table_name = 'id_card_plans'
|
||||||
|
|
||||||
|
has_many :baclight_id_card_plan_benefits, class_name: "Baclight::IdCardPlanBenefit", foreign_key: "plan_id", primary_key: "id"
|
||||||
|
|
||||||
|
alias_attribute :id, :id
|
||||||
|
alias_attribute :title, :title
|
||||||
|
alias_attribute :pb_product_key, :pb_product_key
|
||||||
|
alias_attribute :pl_plan_key, :pl_plan_key
|
||||||
|
alias_attribute :template, :template
|
||||||
|
alias_attribute :setup_id, :setup_id
|
||||||
|
alias_attribute :created_at, :created_at
|
||||||
|
alias_attribute :updated_at, :updated_at
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
module Baclight
|
||||||
|
class IdCardPlanBenefit < BaclightRecord
|
||||||
|
|
||||||
|
self.table_name = 'id_card_plan_benefits'
|
||||||
|
|
||||||
|
belongs_to :baclight_id_card_plan, class_name: "Baclight::IdCardPlan", foreign_key: "plan_id"
|
||||||
|
|
||||||
|
alias_attribute :id, :id
|
||||||
|
alias_attribute :benefit_desc, :benefit_desc
|
||||||
|
alias_attribute :benefit, :benefit
|
||||||
|
alias_attribute :sequence, :sequence
|
||||||
|
alias_attribute :plan_id, :plan_id
|
||||||
|
alias_attribute :created_at, :created_at
|
||||||
|
alias_attribute :updated_at, :updated_at
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
module Baclight
|
||||||
|
class IdCardSetup < BaclightRecord
|
||||||
|
|
||||||
|
self.table_name = 'id_card_setups'
|
||||||
|
|
||||||
|
alias_attribute :id, :id
|
||||||
|
alias_attribute :print_name, :print_name
|
||||||
|
alias_attribute :network_provider, :network_provider
|
||||||
|
alias_attribute :card_template, :card_template
|
||||||
|
alias_attribute :card_color, :card_color
|
||||||
|
alias_attribute :rx_group_number, :rx_group_number
|
||||||
|
alias_attribute :pl_plan_key, :pl_plan_key
|
||||||
|
alias_attribute :has_divisions, :has_divisions
|
||||||
|
alias_attribute :has_dental, :has_dental
|
||||||
|
alias_attribute :active, :active
|
||||||
|
alias_attribute :initialized, :initialized
|
||||||
|
alias_attribute :employer_id, :employer_id
|
||||||
|
alias_attribute :network_logo_id, :network_logo_id
|
||||||
|
alias_attribute :provider_section_id, :provider_section_id
|
||||||
|
alias_attribute :rx_section_id, :rx_section_id
|
||||||
|
alias_attribute :created_at, :created_at
|
||||||
|
alias_attribute :updated_at, :updated_at
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
module Baclight
|
||||||
|
class Member < BaclightRecord
|
||||||
|
|
||||||
|
self.table_name = 'Members'
|
||||||
|
|
||||||
|
belongs_to :baclight_employer, class_name: "Baclight::Employer", foreign_key: "employer_id"
|
||||||
|
|
||||||
|
scope :active, -> {
|
||||||
|
where(active: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
# alias_attribute :id, :id
|
||||||
|
# alias_attribute :name, :name
|
||||||
|
# alias_attribute :family_id, :family_id
|
||||||
|
# alias_attribute :mb_member_key, :mb_member_key
|
||||||
|
# alias_attribute :pb_entity_key, :pb_entity_key
|
||||||
|
# alias_attribute :pl_plan_key, :pl_plan_key
|
||||||
|
# alias_attribute :id_card_display_name, :id_card_display_name
|
||||||
|
# alias_attribute :coverage_class, :coverage_class
|
||||||
|
# alias_attribute :division, :division
|
||||||
|
# alias_attribute :dental_plan_key, :dental_plan_key
|
||||||
|
# alias_attribute :dependents, :dependents
|
||||||
|
# alias_attribute :employer_id, :employer_id
|
||||||
|
# alias_attribute :id_card_plan_id, :id_card_plan_id
|
||||||
|
# alias_attribute :created_at, :created_at
|
||||||
|
# alias_attribute :updated_at, :updated_at
|
||||||
|
|
||||||
|
# def member_belongs_to
|
||||||
|
# {
|
||||||
|
# broker_id: baclight_employer&.broker_id,
|
||||||
|
# carrier_id: baclight_employer&.baclight_broker&.carrier_id
|
||||||
|
# }
|
||||||
|
# end
|
||||||
|
|
||||||
|
def broker_id
|
||||||
|
baclight_employer&.broker_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def carrier_id
|
||||||
|
baclight_employer&.baclight_broker&.carrier_id
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
module Baclight
|
||||||
|
class Provider < BaclightRecord
|
||||||
|
|
||||||
|
self.table_name = 'Providers'
|
||||||
|
|
||||||
|
alias_attribute :id, :id
|
||||||
|
alias_attribute :name, :name
|
||||||
|
alias_attribute :pb_entity_key, :pb_entity_key
|
||||||
|
alias_attribute :tax_id, :tax_id
|
||||||
|
alias_attribute :family_id, :family_id
|
||||||
|
alias_attribute :provider_group_id, :provider_group_id
|
||||||
|
alias_attribute :created_at, :created_at
|
||||||
|
alias_attribute :updated_at, :updated_at
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
class BaclightRecord < ActiveRecord::Base
|
||||||
|
self.abstract_class = true
|
||||||
|
connects_to database: { writing: :baclight, reading: :baclight }
|
||||||
|
end
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
class BlacklistedToken < ApplicationRecord
|
class RetiredToken < ApplicationRecord
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
validates :jti, presence: true, uniqueness: true
|
validates :jti, presence: true, uniqueness: true
|
||||||
validates :exp, presence: true
|
validates :exp, presence: true
|
||||||
|
|
||||||
# check if a token is blacklisted
|
# check if a token is retired
|
||||||
def self.blacklisted?(jti)
|
def self.retired?(jti)
|
||||||
exists?(jti: jti)
|
exists?(jti: jti)
|
||||||
end
|
end
|
||||||
|
|
||||||
+127
-12
@@ -1,17 +1,132 @@
|
|||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
# Include default devise modules. Others available are:
|
include Devise::JWT::RevocationStrategies::JTIMatcher
|
||||||
# :recoverable, :rememberable, :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
|
||||||
devise :database_authenticatable, :registerable,
|
|
||||||
:validatable
|
|
||||||
|
|
||||||
# associations
|
devise :database_authenticatable, :registerable,
|
||||||
has_many :blacklisted_tokens, dependent: :destroy
|
:validatable, :jwt_authenticatable, jwt_revocation_strategy: self
|
||||||
has_many :refresh_tokens, dependent: :destroy
|
# Include default devise modules. Others available are:
|
||||||
|
# :recoverable, :rememberable, :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
||||||
|
|
||||||
# role-based authorization
|
# role-based authorization
|
||||||
enum :role, { user: 0, admin: 1, moderator: 2 }
|
enum :role, { member: 0, employer: 1, broker: 2, provider: 3, carrier: 4, admin: 5 }
|
||||||
|
|
||||||
|
attr_accessor :keychain
|
||||||
|
attr_accessor :date_of_birth
|
||||||
|
attr_accessor :last_four_ssn
|
||||||
|
|
||||||
|
# validations so your db doesn't turn into a trash can
|
||||||
|
validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
||||||
|
validates :password, presence: true, length: { minimum: 6 }, if: -> { new_record? || !password.nil? }
|
||||||
|
|
||||||
|
validates :role, presence: true
|
||||||
|
validates :role_id, presence: true
|
||||||
|
|
||||||
|
validate :role_entity_exists, on: :create
|
||||||
|
|
||||||
|
# after_initialize :keychain
|
||||||
|
before_validation :sync_role_entity, on: :create
|
||||||
|
|
||||||
|
|
||||||
|
def keychain
|
||||||
|
@keychain ||= case role
|
||||||
|
when "member"
|
||||||
|
if role_entity.present?
|
||||||
|
# belongs_to = {
|
||||||
|
# employer_id: role_entity.employer_id,
|
||||||
|
# broker_id: role_entity&.baclight_employer.broker_id,
|
||||||
|
# carrier_id:role_entity&.baclight_employer&.baclight_broker&.carrier_id
|
||||||
|
# }
|
||||||
|
keys = role_entity.attributes.with_indifferent_access.slice(
|
||||||
|
:id, :name, :family_id, :mb_member_key, :pb_entity_key, :pl_plan_key
|
||||||
|
)
|
||||||
|
|
||||||
|
keys #.merge(belongs_to)
|
||||||
|
end
|
||||||
|
when "employer"
|
||||||
|
if role_entity.present?
|
||||||
|
belongs_to = role_entity.joins(broker: :carrier).select(
|
||||||
|
'brokers.id AS broker_id',
|
||||||
|
'carriers.id AS carrier_id'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
keys = role_entity.attributes.with_indifferent_access.slice(
|
||||||
|
:name, :pl_plan_key, :company_pb_entity_key, :plan_id, :group_number
|
||||||
|
)
|
||||||
|
|
||||||
|
keys.merge(belongs_to)
|
||||||
|
end
|
||||||
|
when "broker"
|
||||||
|
if role_entity.present?
|
||||||
|
belongs_to = role_entity.joins(:carrier).select(
|
||||||
|
'carriers.id AS carrier_id'
|
||||||
|
)
|
||||||
|
role_entity.attributes.with_indifferent_access.slice(
|
||||||
|
:name, :id
|
||||||
|
).merge(belongs_to)
|
||||||
|
end
|
||||||
|
when "provider"
|
||||||
|
if role_entity.present?
|
||||||
|
role_entity.attributes.with_indifferent_access.slice(
|
||||||
|
:name, :pb_entity_key, :tax_id, :family_id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
when "carrier"
|
||||||
|
if role_entity.present?
|
||||||
|
role_entity.attributes.with_indifferent_access.slice(
|
||||||
|
:name, :id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
# when "admin"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def role_entity
|
||||||
|
@role_entity = case role
|
||||||
|
when "member"
|
||||||
|
if self.persisted?
|
||||||
|
Baclight::Member.find_by(pb_entity_key: role_id)
|
||||||
|
else
|
||||||
|
find_initial_member
|
||||||
|
end
|
||||||
|
when "employer"
|
||||||
|
Baclight::Employer.find_by(company_pb_entity_key: role_id)
|
||||||
|
when "broker"
|
||||||
|
Baclight::Broker.find_by(broker_id: role_id)
|
||||||
|
when "provider"
|
||||||
|
Baclight::Provider.find_by(tax_id: role_id)
|
||||||
|
when "carrier"
|
||||||
|
Baclight::Carrier.find_by(carrier_id: role_id)
|
||||||
|
# when "admin"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def sync_role_entity
|
||||||
|
if role == "member"
|
||||||
|
self.name = role_entity.name.split(",").reverse.join(" ").squish
|
||||||
|
else
|
||||||
|
self.name = role_entity.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def role_entity_exists
|
||||||
|
if role_entity.blank?
|
||||||
|
errors.add(:role, "#{role} with id #{role_id} was not found.")
|
||||||
|
end
|
||||||
|
if role == "member"
|
||||||
|
dob = Vhcs::VwmbMember.find_by(pb_entity_key: role_id).date_of_birth
|
||||||
|
unless dob == date_of_birth
|
||||||
|
errors.add(:role, "#{role} with id #{role_id} and #{date_of_birth} was not found.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_initial_member
|
||||||
|
initial_member = Vhcs::VwmbMember.where("RIGHT(SocialSecurityNumber, 4) = ? AND DateOfBirth = ?", last_four_ssn, date_of_birth).first
|
||||||
|
self.role_id = initial_member&.pb_entity_key
|
||||||
|
member = Baclight::Member.find_by(pb_entity_key: role_id)
|
||||||
|
member.presence
|
||||||
|
end
|
||||||
|
|
||||||
# validations so your db doesn't turn into a trash can
|
|
||||||
validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
||||||
validates :password, presence: true, length: { minimum: 6 }, if: -> { new_record? || !password.nil? }
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
module Vhcs
|
||||||
|
class VwmbMember < VhcsRecord
|
||||||
|
|
||||||
|
self.table_name = 'VwmbMember'
|
||||||
|
|
||||||
|
alias_attribute :mb_member_key, :MBMemberKey
|
||||||
|
alias_attribute :pb_entity_key, :PBEntityKey
|
||||||
|
alias_attribute :pl_plan_key, :PLPlanKey
|
||||||
|
alias_attribute :mbr_class, :MbrClass
|
||||||
|
alias_attribute :mbr_type, :MbrType
|
||||||
|
alias_attribute :mbr_ap_vendor_key, :MbrAPVendorKey
|
||||||
|
alias_attribute :alt_ap_vendor_key, :AltAPVendorKey
|
||||||
|
alias_attribute :use_alt_payee, :UseAltPayee
|
||||||
|
alias_attribute :send_eob_primary, :SendEOBPrimary
|
||||||
|
alias_attribute :send_eob_dependent, :SendEOBDependent
|
||||||
|
alias_attribute :send_eob_third_party, :SendEOBThirdParty
|
||||||
|
alias_attribute :student_flag, :StudentFlag
|
||||||
|
alias_attribute :disabled_dependent_flag, :DisabledDependentFlag
|
||||||
|
alias_attribute :is_restricted, :IsRestricted
|
||||||
|
alias_attribute :third_party_pb_entity_key, :ThirdPartyPBEntityKey
|
||||||
|
alias_attribute :user_def_1, :UserDef1
|
||||||
|
alias_attribute :user_def_2, :UserDef2
|
||||||
|
alias_attribute :family_id, :FamilyID
|
||||||
|
alias_attribute :sequence_number, :SequenceNumber
|
||||||
|
alias_attribute :enrollee_type_key, :EnrolleeTypeKey
|
||||||
|
alias_attribute :enrollee_type, :EnrolleeType
|
||||||
|
alias_attribute :enrollee_type_value_id, :EnrolleeTypeValueID
|
||||||
|
alias_attribute :sex_key, :SexKey
|
||||||
|
alias_attribute :use_primary_address, :UsePrimaryAddress
|
||||||
|
alias_attribute :birth_date, :BirthDate
|
||||||
|
alias_attribute :birth_sequence_number, :BirthSequenceNumber
|
||||||
|
alias_attribute :death_date, :DeathDate
|
||||||
|
alias_attribute :date_of_birth, :DateOfBirth
|
||||||
|
alias_attribute :date_of_death, :DateOfDeath
|
||||||
|
alias_attribute :social_security_number, :SocialSecurityNumber
|
||||||
|
alias_attribute :hipaaid, :HIPAAID
|
||||||
|
alias_attribute :company_pb_entity_key, :CompanyPBEntityKey
|
||||||
|
alias_attribute :entity_type_id, :EntityTypeID
|
||||||
|
alias_attribute :prefix_id, :PrefixID
|
||||||
|
alias_attribute :first_name, :FirstName
|
||||||
|
alias_attribute :middle_name, :MiddleName
|
||||||
|
alias_attribute :last_name, :LastName
|
||||||
|
alias_attribute :suffix_id, :SuffixID
|
||||||
|
alias_attribute :title, :Title
|
||||||
|
alias_attribute :letter_tag_bit_flags, :LetterTagBitFlags
|
||||||
|
alias_attribute :full_name_last_name_first, :FullNameLastNameFirst
|
||||||
|
alias_attribute :policy_number, :PolicyNumber
|
||||||
|
alias_attribute :send_eob_alt_payee, :SendEOBAltPayee
|
||||||
|
alias_attribute :send_eob_alt_payee_only, :SendEOBAltPayeeOnly
|
||||||
|
|
||||||
|
def attributes
|
||||||
|
rails_like = {
|
||||||
|
mb_member_key: self.mb_member_key,
|
||||||
|
pb_entity_key: self.pb_entity_key,
|
||||||
|
pl_plan_key: self.pl_plan_key,
|
||||||
|
mbr_class: self.mbr_class,
|
||||||
|
mbr_type: self.mbr_type,
|
||||||
|
mbr_ap_vendor_key: self.mbr_ap_vendor_key,
|
||||||
|
alt_ap_vendor_key: self.alt_ap_vendor_key,
|
||||||
|
use_alt_payee: self.use_alt_payee,
|
||||||
|
send_eob_primary: self.send_eob_primary,
|
||||||
|
send_eob_dependent: self.send_eob_dependent,
|
||||||
|
send_eob_third_party: self.send_eob_third_party,
|
||||||
|
student_flag: self.student_flag,
|
||||||
|
disabled_dependent_flag: self.disabled_dependent_flag,
|
||||||
|
is_restricted: self.is_restricted,
|
||||||
|
third_party_pb_entity_key: self.third_party_pb_entity_key,
|
||||||
|
user_def_1: self.user_def_1,
|
||||||
|
user_def_2: self.user_def_2,
|
||||||
|
family_id: self.family_id,
|
||||||
|
sequence_number: self.sequence_number,
|
||||||
|
enrollee_type_key: self.enrollee_type_key,
|
||||||
|
enrollee_type: self.enrollee_type,
|
||||||
|
enrollee_type_value_id: self.enrollee_type_value_id,
|
||||||
|
sex_key: self.sex_key,
|
||||||
|
use_primary_address: self.use_primary_address,
|
||||||
|
birth_date: self.birth_date,
|
||||||
|
birth_sequence_number: self.birth_sequence_number,
|
||||||
|
death_date: self.death_date,
|
||||||
|
date_of_birth: self.date_of_birth,
|
||||||
|
date_of_death: self.date_of_death,
|
||||||
|
social_security_number: self.social_security_number,
|
||||||
|
hipaaid: self.hipaaid,
|
||||||
|
company_pb_entity_key: self.company_pb_entity_key,
|
||||||
|
entity_type_id: self.entity_type_id,
|
||||||
|
prefix_id: self.prefix_id,
|
||||||
|
first_name: self.first_name,
|
||||||
|
middle_name: self.middle_name,
|
||||||
|
last_name: self.last_name,
|
||||||
|
suffix_id: self.suffix_id,
|
||||||
|
title: self.title,
|
||||||
|
letter_tag_bit_flags: self.letter_tag_bit_flags,
|
||||||
|
full_name_last_name_first: self.full_name_last_name_first,
|
||||||
|
policy_number: self.policy_number,
|
||||||
|
send_eob_alt_payee: self.send_eob_alt_payee,
|
||||||
|
send_eob_alt_payee_only: self.send_eob_alt_payee_only,
|
||||||
|
}
|
||||||
|
super.merge(rails_like)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
class VhcsRecord < ActiveRecord::Base
|
||||||
|
self.abstract_class = true
|
||||||
|
connects_to database: { writing: :vhcs, reading: :vhcs }
|
||||||
|
end
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ApplicationPolicy
|
||||||
|
attr_reader :user, :record
|
||||||
|
|
||||||
|
def initialize(user, record)
|
||||||
|
@user = user
|
||||||
|
@record = record
|
||||||
|
end
|
||||||
|
|
||||||
|
def index?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def show?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def create?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def new?
|
||||||
|
create?
|
||||||
|
end
|
||||||
|
|
||||||
|
def update?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit?
|
||||||
|
update?
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
class Scope
|
||||||
|
def initialize(user, scope)
|
||||||
|
@user = user
|
||||||
|
@scope = scope
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve
|
||||||
|
raise NoMethodError, "You must define #resolve in #{self.class}"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_reader :user, :scope
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
class Baclight::BrokerPolicy < ApplicationPolicy
|
||||||
|
|
||||||
|
def employers_list?; true; end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def broker_rule?
|
||||||
|
user.admin? ||
|
||||||
|
(user.broker? && record.broker_id == user.keychain[:id]) ||
|
||||||
|
(user.carrier? && record.carrier_id == user.keychain[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
class Baclight::EmployerPolicy < ApplicationPolicy
|
||||||
|
|
||||||
|
def id_cards?; employer_rule?; end
|
||||||
|
def members_list?; true; end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def employer_rule?
|
||||||
|
user.admin? ||
|
||||||
|
(user.employer? && record.employer_id == user.keychain[:id]) ||
|
||||||
|
(user.broker? && record.broker_id == user.keychain[:id]) ||
|
||||||
|
(user.carrier? && record.carrier_id == user.keychain[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
class Baclight::MemberPolicy < ApplicationPolicy
|
||||||
|
|
||||||
|
def initialize_dashboard?; member_rule?; end
|
||||||
|
def id_card?; member_rule?; end
|
||||||
|
|
||||||
|
def claims?
|
||||||
|
user.admin? ||
|
||||||
|
(user.member? && record.id == user.keychain[:id]) ||
|
||||||
|
(user.carrier? && record.carrier_id == user.keychain[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def member_rule?
|
||||||
|
user.admin? ||
|
||||||
|
(user.member? && record.id == user.keychain[:id]) ||
|
||||||
|
(user.employer? && record.employer_id == user.keychain[:id]) ||
|
||||||
|
(user.broker? && record.broker_id == user.keychain[:id]) ||
|
||||||
|
(user.carrier? && record.carrier_id == user.keychain[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
class IdCardsPolicy < ApplicationPolicy
|
||||||
|
# Corresponds to PostController#edit or #update
|
||||||
|
def member_card?
|
||||||
|
user.admin? ||
|
||||||
|
(user.carrier? && record.belongs_to_levels.carrier_id == user.keychain.id) ||
|
||||||
|
(user.broker? && record.belongs_to_levels.broker_id == user.keychain.id) ||
|
||||||
|
(user.employer? && record.pl_plan_key == user.keychain.pl_plan_key) ||
|
||||||
|
(user.member? && record.pb_entity_key == user.keychain.pb_entity_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Corresponds to PostController#destroy
|
||||||
|
def employer_cards?
|
||||||
|
user.admin? ||
|
||||||
|
(user.carrier? && record.belongs_to_levels.carrier_id == user.keychain.id) ||
|
||||||
|
(user.broker? && record.belongs_to_levels.broker_id == user.keychain.id) ||
|
||||||
|
(user.employer? && record.pl_plan_key == user.keychain.pl_plan_key)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
class CallStoredProc
|
||||||
|
def initialize(procedure_name, args = {})
|
||||||
|
@procedure_name = procedure_name
|
||||||
|
@args = args
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
params_sql = @args.map { |key, value| "@#{key} = :#{key}" }.join(', ')
|
||||||
|
sql_template = "EXEC #{@procedure_name} #{params_sql}"
|
||||||
|
sanitized_sql = VhcsRecord.send(:sanitize_sql_array, [sql_template, @args])
|
||||||
|
|
||||||
|
VhcsRecord.connection.exec_query(sanitized_sql)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
module EmployersService
|
||||||
|
class IdCards
|
||||||
|
|
||||||
|
def initialize(pl_plan_key)
|
||||||
|
@employer = Baclight::Employer.find_by(pl_plan_key: pl_plan_key)
|
||||||
|
if @employer.present?
|
||||||
|
@pl_plan_key = pl_plan_key
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Member not found."
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
url_components = {
|
||||||
|
host: ENV["BACLIGHT_SERVER_HOST"],
|
||||||
|
port: ENV["BACLIGHT_SERVER_PORT"],
|
||||||
|
path: "/api/v1/web_id_cards/employer_cards/#{@pl_plan_key}"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = HTTParty.get(
|
||||||
|
URI::HTTP.build(url_components),
|
||||||
|
headers: { 'Content-Type' => 'application/json', 'Accept' => 'application/zip' },
|
||||||
|
stream_body: true
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.code == 200
|
||||||
|
# content_disposition = response.headers['Content-Disposition']
|
||||||
|
# {
|
||||||
|
# zip_file: response.body,
|
||||||
|
# filename: content_disposition[/filename="?([^"]*)"?/, 1]
|
||||||
|
# }
|
||||||
|
|
||||||
|
response
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
class MemberClaimsService
|
||||||
|
|
||||||
|
def initialize(pb_entity_key)
|
||||||
|
@member = Baclight::Member.find_by(pb_entity_key: pb_entity_key)
|
||||||
|
if @member.present?
|
||||||
|
@pl_plan_key = @member.pl_plan_key
|
||||||
|
@family_id = @member.family_id
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Member not found."
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
args = {
|
||||||
|
PLPlanKey: @pl_plan_key,
|
||||||
|
FamilyId: @family_id,
|
||||||
|
ProviderTaxId: "",
|
||||||
|
UserID: "JASONJ"
|
||||||
|
}
|
||||||
|
claims_headers = CallStoredProc.new("CPFindClaim", args).call
|
||||||
|
|
||||||
|
recent_claims_headers = claims_headers.sort_by { |ch| ch['IncurDate'].to_date }.reverse.first(6)
|
||||||
|
|
||||||
|
recent_claims_headers.map { |ch| { claim_number: ch['CPHeaderKey'], visit_date: ch['IncurDate'].strftime("%m-%d-%Y"), claim_status: ch['AdjudicatedStatusShortDesc'], patient_name: ch['FullName'], provider_name: ch['ProviderCompany']} }
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# claimHeaders = _vContext.ClaimHeader.FromSql("dbo.CPFindClaim
|
||||||
|
# @PLPlanKey = {0}, @CSCaseKey = {1}, @PAPriorAuthKey = {2}, @FromDate = {3}, @ToDate = {4}, @FamilyID = {5}, @FirstName = {6}, @LastName = {7}, @SequenceNumber = {8}, @PBEntityKey = {9}, @ProviderIdentifierCode = {10}, @ProviderCompanyName = {11}, @ProviderTaxID = {12}, @HeaderStatusValueID = {13}, @AdjudicatedStatusValueID = {14}, @EventUSUserKey = {15}, @IncludeRestricted = {16}, @ClaimTypeValueID = {17}, @ClaimSource = {18}, @ReceivedFromDate = {19}, @ReceivedToDate = {20}, @DCN = {21}, @SSN = {22}, @SSN_IncFamily = {23}, @SearchAllIDs = {24}, @UserID = {25}, @ExecuteOrDisplay = {26} ",
|
||||||
|
# PlanKey, 0, 0, FromDate, ToDate, FamilyId, "", "", -1, 0, "", "", ProviderTaxId, 0, 0, 0, 0, 0, 2, null, null, null, null, 0, 0, "DAVIDH", 1)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
module MembersService
|
||||||
|
class Claims
|
||||||
|
|
||||||
|
def initialize(entity, recent = false)
|
||||||
|
if entity.is_a?(Integer)
|
||||||
|
@member = Baclight::Member.find_by(pb_entity_key: entity)
|
||||||
|
else
|
||||||
|
@member = entity
|
||||||
|
end
|
||||||
|
if @member.present?
|
||||||
|
@pl_plan_key = @member.pl_plan_key
|
||||||
|
@family_id = @member.family_id
|
||||||
|
@recent = recent
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Member not found."
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
args = {
|
||||||
|
PLPlanKey: @pl_plan_key,
|
||||||
|
FamilyId: @family_id,
|
||||||
|
ProviderTaxId: "",
|
||||||
|
UserID: "JASONJ"
|
||||||
|
}
|
||||||
|
claims_headers = CallStoredProc.new("CPFindClaim", args).call
|
||||||
|
|
||||||
|
ordered_claims_headers = claims_headers.sort_by { |ch| ch['IncurDate'].to_date }.reverse
|
||||||
|
ordered_claims_headers = ordered_claims_headers.first(6) if @recent
|
||||||
|
|
||||||
|
ordered_claims_headers.map { |ch| { claim_number: ch['CPHeaderKey'], visit_date: ch['IncurDate'].strftime("%m-%d-%Y"), claim_status: ch['AdjudicatedStatusShortDesc'], patient_name: ch['FullName'], provider_name: ch['ProviderCompany']} } || []
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# claimHeaders = _vContext.ClaimHeader.FromSql("dbo.CPFindClaim
|
||||||
|
# @PLPlanKey = {0}, @CSCaseKey = {1}, @PAPriorAuthKey = {2}, @FromDate = {3}, @ToDate = {4}, @FamilyID = {5}, @FirstName = {6}, @LastName = {7}, @SequenceNumber = {8}, @PBEntityKey = {9}, @ProviderIdentifierCode = {10}, @ProviderCompanyName = {11}, @ProviderTaxID = {12}, @HeaderStatusValueID = {13}, @AdjudicatedStatusValueID = {14}, @EventUSUserKey = {15}, @IncludeRestricted = {16}, @ClaimTypeValueID = {17}, @ClaimSource = {18}, @ReceivedFromDate = {19}, @ReceivedToDate = {20}, @DCN = {21}, @SSN = {22}, @SSN_IncFamily = {23}, @SearchAllIDs = {24}, @UserID = {25}, @ExecuteOrDisplay = {26} ",
|
||||||
|
# PlanKey, 0, 0, FromDate, ToDate, FamilyId, "", "", -1, 0, "", "", ProviderTaxId, 0, 0, 0, 0, 0, 2, null, null, null, null, 0, 0, "DAVIDH", 1)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
module MembersService
|
||||||
|
class IdCard
|
||||||
|
|
||||||
|
def initialize(member, layout)
|
||||||
|
@member = member
|
||||||
|
if @member.present?
|
||||||
|
@pb_entity_key = @member.pb_entity_key
|
||||||
|
@layout = layout
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Member not found."
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
url_components = {
|
||||||
|
host: ENV["BACLIGHT_SERVER_HOST"],
|
||||||
|
port: ENV["BACLIGHT_SERVER_PORT"],
|
||||||
|
path: "/api/v1/web_id_cards/member_card/#{@pb_entity_key}/#{@layout}"
|
||||||
|
}
|
||||||
|
|
||||||
|
response = HTTParty.get(
|
||||||
|
URI::HTTP.build(url_components),
|
||||||
|
headers: { 'Content-Type' => 'application/json', 'Accept' => 'application/pdf' },
|
||||||
|
stream_body: true
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.code == 200
|
||||||
|
# content_disposition = response.headers['Content-Disposition']
|
||||||
|
# {
|
||||||
|
# pdf_file: response.body,
|
||||||
|
# filename: content_disposition[/filename="?([^"]*)"?/, 1]
|
||||||
|
# }
|
||||||
|
|
||||||
|
response
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
module MembersService
|
||||||
|
class InitializeDashboard
|
||||||
|
|
||||||
|
def initialize(member)
|
||||||
|
@member = member
|
||||||
|
unless @member.present?
|
||||||
|
raise ArgumentError, "Member not found."
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
plan_benefits = MembersService::PlanBenefits.new(@member).call
|
||||||
|
network_provider = MembersService::NetworkProvider.new(@member).call
|
||||||
|
recent_claims = MembersService::Claims.new(@member, true).call
|
||||||
|
|
||||||
|
{
|
||||||
|
network_provider: network_provider,
|
||||||
|
plan_benefits: plan_benefits,
|
||||||
|
recent_claims: recent_claims
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
module MembersService
|
||||||
|
class NetworkProvider
|
||||||
|
|
||||||
|
def initialize(member)
|
||||||
|
@member = member
|
||||||
|
if @member.present?
|
||||||
|
@pl_plan_key = @member.pl_plan_key
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Member not found."
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
Baclight::IdCardSetup.find_by(pl_plan_key: @pl_plan_key).network_provider
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
module MembersService
|
||||||
|
class PlanBenefits
|
||||||
|
|
||||||
|
def initialize(member)
|
||||||
|
@member = member
|
||||||
|
if @member.present?
|
||||||
|
@plan_id = @member.id_card_plan_id
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Member not found."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
Baclight::IdCardPlan.joins(:baclight_id_card_plan_benefits)
|
||||||
|
.select('id_card_plans.id, id_card_plans.title, id_card_plan_benefits.id, id_card_plan_benefits.sequence, id_card_plan_benefits.benefit, id_card_plan_benefits.benefit_desc')
|
||||||
|
.where(id: @plan_id)
|
||||||
|
.distinct
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Executable → Regular
Executable → Regular
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
if ! gem list foreman -i --silent; then
|
||||||
|
echo "Installing foreman..."
|
||||||
|
gem install foreman
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Default to port 3000 if not specified
|
||||||
|
export PORT="${PORT:-3005}"
|
||||||
|
|
||||||
|
# Let the debug gem allow remote connections,
|
||||||
|
# but avoid loading until `debugger` is called
|
||||||
|
export RUBY_DEBUG_OPEN="true"
|
||||||
|
export RUBY_DEBUG_LAZY="true"
|
||||||
|
|
||||||
|
exec foreman start -f Procfile.dev "$@"
|
||||||
Executable → Regular
+7
@@ -10,4 +10,11 @@ if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then
|
|||||||
./bin/rails db:prepare
|
./bin/rails db:prepare
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
rm -f /usr/src/app/tmp/pids/server.pid
|
||||||
|
|
||||||
|
rm -rf /usr/local/bundle/cache/*.gem
|
||||||
|
|
||||||
|
echo "bundle install..."
|
||||||
|
bundle check || bundle install --jobs 4
|
||||||
|
|
||||||
exec "${@}"
|
exec "${@}"
|
||||||
|
|||||||
Executable → Regular
@@ -41,5 +41,12 @@ module RailsApiAuthTemplate
|
|||||||
# Skip views, helpers and assets when generating a new resource.
|
# Skip views, helpers and assets when generating a new resource.
|
||||||
config.api_only = true
|
config.api_only = true
|
||||||
config.middleware.use Rack::Attack
|
config.middleware.use Rack::Attack
|
||||||
|
|
||||||
|
# 1. Configure a session store type and key names
|
||||||
|
config.session_store :cookie_store, key: '_britton_api_session'
|
||||||
|
|
||||||
|
# 2. Re-include the required middlewares for cookies and sessions
|
||||||
|
config.middleware.use ActionDispatch::Cookies
|
||||||
|
config.middleware.use config.session_store, config.session_options
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+93
-76
@@ -1,85 +1,102 @@
|
|||||||
# PostgreSQL. Versions 9.3 and up are supported.
|
production:
|
||||||
#
|
primary:
|
||||||
# Install the pg driver:
|
adapter: sqlserver
|
||||||
# gem install pg
|
mode: dblib
|
||||||
# On macOS with Homebrew:
|
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||||
# gem install pg -- --with-pg-config=/usr/local/bin/pg_config
|
|
||||||
# On Windows:
|
host: 10.41.82.73
|
||||||
# gem install pg
|
port: 1433
|
||||||
# Choose the win32 build.
|
database: BrittonWeb
|
||||||
# Install PostgreSQL and put its /bin directory on your path.
|
username: BSTI
|
||||||
#
|
password: BSTIBOY
|
||||||
# Configure Using Gemfile
|
tds_version: 7.3
|
||||||
# gem "pg"
|
|
||||||
#
|
vhcs:
|
||||||
default: &default
|
adapter: sqlserver
|
||||||
adapter: postgresql
|
mode: dblib
|
||||||
encoding: unicode
|
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||||
# For details on connection pooling, see Rails configuration guide
|
|
||||||
# https://guides.rubyonrails.org/configuring.html#database-pooling
|
host: "10.41.82.72"
|
||||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
port: 1433
|
||||||
|
database: VHCS_HIPAA
|
||||||
|
username: BSTI
|
||||||
|
password: BSTIBOY
|
||||||
|
database_tasks: false
|
||||||
|
tds_version: 7.3
|
||||||
|
|
||||||
|
baclight:
|
||||||
|
adapter: sqlserver
|
||||||
|
mode: dblib
|
||||||
|
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||||
|
|
||||||
|
host: "10.41.82.73"
|
||||||
|
port: 1433
|
||||||
|
database: Baclight
|
||||||
|
username: BSTI
|
||||||
|
password: BSTIBOY
|
||||||
|
database_tasks: false
|
||||||
|
tds_version: 7.3
|
||||||
|
|
||||||
|
heb_web:
|
||||||
|
adapter: sqlserver
|
||||||
|
mode: dblib
|
||||||
|
host: 10.41.82.73
|
||||||
|
port: 1433
|
||||||
|
database: HEBWeb
|
||||||
|
username: SA
|
||||||
|
password: Adm1nBb5
|
||||||
|
pool: 5
|
||||||
|
database_tasks: false
|
||||||
|
tds_version: 7.3
|
||||||
|
|
||||||
|
|
||||||
development:
|
development:
|
||||||
<<: *default
|
primary:
|
||||||
database: rails_api_auth_template_development
|
adapter: sqlserver
|
||||||
|
mode: dblib
|
||||||
|
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||||
|
|
||||||
# The specified database role being used to connect to PostgreSQL.
|
host: 10.41.82.73
|
||||||
# To create additional roles in PostgreSQL see `$ createuser --help`.
|
port: 1433
|
||||||
# When left blank, PostgreSQL will use the default role. This is
|
database: BrittonWeb
|
||||||
# the same name as the operating system user running Rails.
|
username: BSTI
|
||||||
#username: rails_api_auth_template
|
password: BSTIBOY
|
||||||
|
tds_version: 7.3
|
||||||
|
|
||||||
# The password associated with the PostgreSQL role (username).
|
vhcs:
|
||||||
#password:
|
adapter: sqlserver
|
||||||
|
mode: dblib
|
||||||
|
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||||
|
|
||||||
# Connect on a TCP socket. Omitted by default since the client uses a
|
host: "10.41.82.73"
|
||||||
# domain socket that doesn't need configuration. Windows does not have
|
port: 1433
|
||||||
# domain sockets, so uncomment these lines.
|
database: VHCS_HIPAA
|
||||||
#host: localhost
|
username: BSTI
|
||||||
|
password: BSTIBOY
|
||||||
|
database_tasks: false
|
||||||
|
tds_version: 7.3
|
||||||
|
|
||||||
# The TCP port the server listens on. Defaults to 5432.
|
baclight:
|
||||||
# If your server runs on a different port number, change accordingly.
|
adapter: sqlserver
|
||||||
#port: 5432
|
mode: dblib
|
||||||
|
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||||
|
|
||||||
# Schema search path. The server defaults to $user,public
|
host: "10.41.82.73"
|
||||||
#schema_search_path: myapp,sharedapp,public
|
port: 1433
|
||||||
|
database: Baclight
|
||||||
|
username: BSTI
|
||||||
|
password: BSTIBOY
|
||||||
|
database_tasks: false
|
||||||
|
tds_version: 7.3
|
||||||
|
|
||||||
# Minimum log levels, in increasing order:
|
heb_web:
|
||||||
# debug5, debug4, debug3, debug2, debug1,
|
adapter: sqlserver
|
||||||
# log, notice, warning, error, fatal, and panic
|
mode: dblib
|
||||||
# Defaults to warning.
|
host: 10.41.82.73
|
||||||
#min_messages: notice
|
port: 1433
|
||||||
|
database: HEBWeb
|
||||||
# Warning: The database defined as "test" will be erased and
|
username: SA
|
||||||
# re-generated from your development database when you run "rake".
|
password: Adm1nBb5
|
||||||
# Do not set this db to the same as development or production.
|
pool: 5
|
||||||
test:
|
database_tasks: false
|
||||||
<<: *default
|
tds_version: 7.3
|
||||||
database: rails_api_auth_template_test
|
|
||||||
|
|
||||||
# As with config/credentials.yml, you never want to store sensitive information,
|
|
||||||
# like your database password, in your source code. If your source code is
|
|
||||||
# ever seen by anyone, they now have access to your database.
|
|
||||||
#
|
|
||||||
# Instead, provide the password or a full connection URL as an environment
|
|
||||||
# variable when you boot the app. For example:
|
|
||||||
#
|
|
||||||
# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
|
|
||||||
#
|
|
||||||
# If the connection URL is provided in the special DATABASE_URL environment
|
|
||||||
# variable, Rails will automatically merge its configuration values on top of
|
|
||||||
# the values provided in this file. Alternatively, you can specify a connection
|
|
||||||
# URL environment variable explicitly:
|
|
||||||
#
|
|
||||||
# production:
|
|
||||||
# url: <%= ENV["MY_APP_DATABASE_URL"] %>
|
|
||||||
#
|
|
||||||
# Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
|
|
||||||
# for a full overview on how database connection configuration can be specified.
|
|
||||||
#
|
|
||||||
production:
|
|
||||||
<<: *default
|
|
||||||
database: rails_api_auth_template_production
|
|
||||||
username: rails_api_auth_template
|
|
||||||
password: <%= ENV["RAILS_API_AUTH_TEMPLATE_DATABASE_PASSWORD"] %>
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ Rails.application.configure do
|
|||||||
# Enable server timing.
|
# Enable server timing.
|
||||||
config.server_timing = true
|
config.server_timing = true
|
||||||
|
|
||||||
|
config.hosts << "web_api"
|
||||||
|
|
||||||
# Enable/disable caching. By default caching is disabled.
|
# Enable/disable caching. By default caching is disabled.
|
||||||
# Run rails dev:cache to toggle caching.
|
# Run rails dev:cache to toggle caching.
|
||||||
if Rails.root.join("tmp/caching-dev.txt").exist?
|
if Rails.root.join("tmp/caching-dev.txt").exist?
|
||||||
|
|||||||
@@ -7,10 +7,11 @@
|
|||||||
|
|
||||||
Rails.application.config.middleware.insert_before 0, Rack::Cors do
|
Rails.application.config.middleware.insert_before 0, Rack::Cors do
|
||||||
allow do
|
allow do
|
||||||
origins "*"
|
origins 'http://localhost:3001'
|
||||||
|
|
||||||
resource "*",
|
resource "*",
|
||||||
headers: :any,
|
headers: :any,
|
||||||
methods: [ :get, :post, :put, :patch, :delete, :options, :head ]
|
methods: [ :get, :post, :put, :patch, :delete, :options, :head ],
|
||||||
|
credentials: true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -16,6 +16,17 @@ Devise.setup do |config|
|
|||||||
# by default. You can change it below and use your own secret key.
|
# by default. You can change it below and use your own secret key.
|
||||||
# config.secret_key = '48e5fef13ecab7262e95624bd16f02e6a4ce11f2d44f1e18851e3e046535a6b6c233c94d1eb28e62655a12a63acb9ead3426ca1cb36753fa33f2970cd5377c3b'
|
# config.secret_key = '48e5fef13ecab7262e95624bd16f02e6a4ce11f2d44f1e18851e3e046535a6b6c233c94d1eb28e62655a12a63acb9ead3426ca1cb36753fa33f2970cd5377c3b'
|
||||||
|
|
||||||
|
config.jwt do |jwt|
|
||||||
|
jwt.secret = ENV['DEVISE_JWT_SECRET_KEY'] # Or Rails.application.credentials.devise_jwt_secret_key!
|
||||||
|
jwt.dispatch_requests = [
|
||||||
|
['POST', %r{^/login$}]
|
||||||
|
]
|
||||||
|
jwt.revocation_requests = [
|
||||||
|
['DELETE', %r{^/logout$}]
|
||||||
|
]
|
||||||
|
jwt.expiration_time = 30.minutes.to_i
|
||||||
|
end
|
||||||
|
|
||||||
# ==> Controller configuration
|
# ==> Controller configuration
|
||||||
# Configure the parent class to the devise controllers.
|
# Configure the parent class to the devise controllers.
|
||||||
# config.parent_controller = 'DeviseController'
|
# config.parent_controller = 'DeviseController'
|
||||||
|
|||||||
@@ -4,5 +4,5 @@
|
|||||||
# Use this to limit dissemination of sensitive information.
|
# Use this to limit dissemination of sensitive information.
|
||||||
# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
|
# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
|
||||||
Rails.application.config.filter_parameters += [
|
Rails.application.config.filter_parameters += [
|
||||||
:passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
|
:secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
|
||||||
]
|
]
|
||||||
|
|||||||
+45
-7
@@ -1,7 +1,17 @@
|
|||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
mount Rswag::Ui::Engine => "/api-docs"
|
mount Rswag::Ui::Engine => "/api-docs"
|
||||||
mount Rswag::Api::Engine => "/api-docs"
|
mount Rswag::Api::Engine => "/api-docs"
|
||||||
devise_for :users, skip: [ :registrations, :passwords, :confirmations ]
|
devise_for :users,
|
||||||
|
path: '',
|
||||||
|
path_names: {
|
||||||
|
sign_in: 'signin',
|
||||||
|
sign_out: 'signout',
|
||||||
|
registration: 'signup'
|
||||||
|
},
|
||||||
|
controllers: {
|
||||||
|
sessions: 'users/sessions',
|
||||||
|
registrations: 'users/registrations'
|
||||||
|
}
|
||||||
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
|
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
|
||||||
|
|
||||||
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
|
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
|
||||||
@@ -10,16 +20,44 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
namespace :api do
|
namespace :api do
|
||||||
namespace :v1 do
|
namespace :v1 do
|
||||||
post "signup", to: "auth#signup"
|
# post "signup", to: "auth#signup"
|
||||||
post "login", to: "auth#login"
|
# post "login", to: "auth#login"
|
||||||
post "logout", to: "auth#logout"
|
# post "logout", to: "auth#logout"
|
||||||
post "refresh", to: "auth#refresh"
|
# post "refresh", to: "auth#refresh"
|
||||||
|
|
||||||
get "protected", to: "protected#index"
|
# get "protected", to: "protected#index"
|
||||||
get "profile", to: "profile#show"
|
# get "profile", to: "profile#show"
|
||||||
|
|
||||||
# admin routes
|
# admin routes
|
||||||
get "admin/dashboard", to: "admin#dashboard"
|
get "admin/dashboard", to: "admin#dashboard"
|
||||||
|
|
||||||
|
get 'id_cards/member_card/:id/:layout', to: 'id_cards#member_card'
|
||||||
|
get 'id_cards/member_benefits/:id', to: 'id_cards#member_benefits'
|
||||||
|
get 'id_cards/employer_cards/:id', to: 'id_cards#employer_cards'
|
||||||
|
|
||||||
|
get 'claims/member_claims/:id', to: 'claims#member_claims'
|
||||||
|
|
||||||
|
get 'network_provider/employer_network_provider/:id', to: 'network_provider#employer_network_provider'
|
||||||
|
|
||||||
|
resources :members do
|
||||||
|
member do
|
||||||
|
get 'initialize_dashboard'
|
||||||
|
get 'id_card/:layout', to: 'members#id_card'
|
||||||
|
get 'claims'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
resources :employers do
|
||||||
|
member do
|
||||||
|
get 'initialize_dashboard'
|
||||||
|
get 'id_cards'
|
||||||
|
get 'members_list'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
resources :brokers do
|
||||||
|
member do
|
||||||
|
get 'employers_list'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ class DeviseCreateUsers < ActiveRecord::Migration[7.2]
|
|||||||
t.string :email, null: false, default: ""
|
t.string :email, null: false, default: ""
|
||||||
t.string :encrypted_password, null: false, default: ""
|
t.string :encrypted_password, null: false, default: ""
|
||||||
|
|
||||||
|
t.string :name, default: ""
|
||||||
|
t.integer :role, null: false
|
||||||
|
t.integer :role_id, null: false
|
||||||
|
|
||||||
|
t.string :jti, null: false
|
||||||
|
|
||||||
## Recoverable
|
## Recoverable
|
||||||
t.string :reset_password_token
|
t.string :reset_password_token
|
||||||
t.datetime :reset_password_sent_at
|
t.datetime :reset_password_sent_at
|
||||||
@@ -37,7 +43,8 @@ class DeviseCreateUsers < ActiveRecord::Migration[7.2]
|
|||||||
end
|
end
|
||||||
|
|
||||||
add_index :users, :email, unique: true
|
add_index :users, :email, unique: true
|
||||||
add_index :users, :reset_password_token, unique: true
|
add_index :users, :reset_password_token, unique: false
|
||||||
|
add_index :users, :jti, unique: true
|
||||||
# add_index :users, :confirmation_token, unique: true
|
# add_index :users, :confirmation_token, unique: true
|
||||||
# add_index :users, :unlock_token, unique: true
|
# add_index :users, :unlock_token, unique: true
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
class CreateBlacklistedTokens < ActiveRecord::Migration[7.2]
|
|
||||||
def change
|
|
||||||
create_table :blacklisted_tokens do |t|
|
|
||||||
t.string :jti, null: false
|
|
||||||
t.references :user, null: false, foreign_key: true
|
|
||||||
t.datetime :exp, null: false
|
|
||||||
|
|
||||||
t.timestamps
|
|
||||||
end
|
|
||||||
|
|
||||||
add_index :blacklisted_tokens, :jti, unique: true
|
|
||||||
add_index :blacklisted_tokens, :exp
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
class AddRoleToUsers < ActiveRecord::Migration[7.2]
|
|
||||||
def change
|
|
||||||
add_column :users, :role, :integer, default: 0, null: false
|
|
||||||
add_index :users, :role
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
class CreateRetiredTokens < ActiveRecord::Migration[7.2]
|
||||||
|
def change
|
||||||
|
create_table :retired_tokens do |t|
|
||||||
|
t.string :jti, null: false
|
||||||
|
t.references :user, null: false, foreign_key: true
|
||||||
|
t.datetime :exp, null: false
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :retired_tokens, :jti, unique: true
|
||||||
|
add_index :retired_tokens, :exp
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
class AddRoleAndNameToUsers < ActiveRecord::Migration[7.2]
|
||||||
|
def change
|
||||||
|
# add_column :users, :role, :integer, default: 0, null: false
|
||||||
|
# add_column :users, :role_id, :integer, null: false
|
||||||
|
# add_column :users, :name, :string, null: false
|
||||||
|
|
||||||
|
# add_index :users, :role
|
||||||
|
end
|
||||||
|
end
|
||||||
Generated
+7
-33
@@ -10,47 +10,21 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.2].define(version: 2026_01_28_000003) do
|
ActiveRecord::Schema[7.2].define(version: 2025_06_13_174349) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
|
||||||
enable_extension "plpgsql"
|
|
||||||
|
|
||||||
create_table "blacklisted_tokens", force: :cascade do |t|
|
|
||||||
t.string "jti", null: false
|
|
||||||
t.bigint "user_id", null: false
|
|
||||||
t.datetime "exp", null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.datetime "updated_at", null: false
|
|
||||||
t.index ["exp"], name: "index_blacklisted_tokens_on_exp"
|
|
||||||
t.index ["jti"], name: "index_blacklisted_tokens_on_jti", unique: true
|
|
||||||
t.index ["user_id"], name: "index_blacklisted_tokens_on_user_id"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "refresh_tokens", force: :cascade do |t|
|
|
||||||
t.string "token", null: false
|
|
||||||
t.bigint "user_id", null: false
|
|
||||||
t.datetime "expires_at", null: false
|
|
||||||
t.boolean "revoked", default: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.datetime "updated_at", null: false
|
|
||||||
t.index ["token"], name: "index_refresh_tokens_on_token", unique: true
|
|
||||||
t.index ["user_id", "revoked"], name: "index_refresh_tokens_on_user_id_and_revoked"
|
|
||||||
t.index ["user_id"], name: "index_refresh_tokens_on_user_id"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "users", force: :cascade do |t|
|
create_table "users", force: :cascade do |t|
|
||||||
t.string "email", default: "", null: false
|
t.string "email", default: "", null: false
|
||||||
t.string "encrypted_password", default: "", null: false
|
t.string "encrypted_password", default: "", null: false
|
||||||
|
t.string "name", default: ""
|
||||||
|
t.integer "role", null: false
|
||||||
|
t.integer "role_id", null: false
|
||||||
|
t.string "jti", null: false
|
||||||
t.string "reset_password_token"
|
t.string "reset_password_token"
|
||||||
t.datetime "reset_password_sent_at"
|
t.datetime "reset_password_sent_at"
|
||||||
t.datetime "remember_created_at"
|
t.datetime "remember_created_at"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.integer "role", default: 0, null: false
|
|
||||||
t.index ["email"], name: "index_users_on_email", unique: true
|
t.index ["email"], name: "index_users_on_email", unique: true
|
||||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
t.index ["jti"], name: "index_users_on_jti", unique: true
|
||||||
t.index ["role"], name: "index_users_on_role"
|
t.index ["reset_password_token"], name: "index_users_on_reset_password_token"
|
||||||
end
|
end
|
||||||
|
|
||||||
add_foreign_key "blacklisted_tokens", "users"
|
|
||||||
add_foreign_key "refresh_tokens", "users"
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# ----- Build Stage -----
|
||||||
|
# Using a specific Ruby version (e.g., 3.3) for stability
|
||||||
|
ARG RUBY_VERSION=3.3.1
|
||||||
|
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
|
||||||
|
|
||||||
|
RUN apt-get update -qq && \
|
||||||
|
apt-get install --no-install-recommends -y curl libjemalloc2 libvips tdsodbc freetds-dev build-essential libpq-dev libyaml-dev && \
|
||||||
|
rm -rf /var/lib/apt/lists /var/cache/apt/archives
|
||||||
|
|
||||||
|
ENV LANG=C.UTF-8 \
|
||||||
|
BUNDLE_JOBS=4 \
|
||||||
|
BUNDLE_RETRY=3
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
|
||||||
|
RUN groupadd --system --gid 1000 rails && \
|
||||||
|
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
|
||||||
|
chown -R rails:rails /usr/src/app
|
||||||
|
USER 1000:1000
|
||||||
|
|
||||||
|
# COPY bin/development-entrypoint.sh /bin/development-entrypoint.sh
|
||||||
|
# RUN chown root:root bin/development-entrypoint.sh && chmod +x bin/development-entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["./bin/docker-entrypoint"]
|
||||||
|
|
||||||
|
# ENTRYPOINT ["./bin/cron-entrypoint"]
|
||||||
|
|
||||||
|
|
||||||
|
# Expose the application port
|
||||||
|
EXPOSE 3005
|
||||||
|
|
||||||
|
# Set the default command to run the Rails server
|
||||||
|
CMD ["./bin/dev"]
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: development.Dockerfile
|
||||||
|
container_name: web_api
|
||||||
|
volumes:
|
||||||
|
- .:/usr/src/app
|
||||||
|
- bundle:/usr/local/bundle
|
||||||
|
ports:
|
||||||
|
- "3005:3005"
|
||||||
|
environment:
|
||||||
|
- HISTFILE=/usr/src/app/log/.bash_history
|
||||||
|
- RAILS_ENV=development
|
||||||
|
- BACLIGHT_SERVER_HOST=baclight
|
||||||
|
- BACLIGHT_SERVER_PORT=3002
|
||||||
|
- PUID=1000
|
||||||
|
- PGID=100
|
||||||
|
- SECRET_KEY_BASE=${SECRET_KEY_BASE}
|
||||||
|
- DEVISE_JWT_SECRET_KEY=${DEVISE_JWT_SECRET_KEY}
|
||||||
|
tty: true
|
||||||
|
stdin_open: true
|
||||||
|
networks:
|
||||||
|
- britton_network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
britton_network:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
bundle:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
Description:
|
||||||
|
Explain the generator
|
||||||
|
|
||||||
|
Example:
|
||||||
|
bin/rails generate legacy_db_model Thing
|
||||||
|
|
||||||
|
This will create:
|
||||||
|
what/will/it/create
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
class LegacyDbModelGenerator < Rails::Generators::Base
|
||||||
|
source_root File.expand_path('templates', __dir__)
|
||||||
|
|
||||||
|
argument :db_name, type: :string, required: true
|
||||||
|
argument :table_name, type: :string, required: true
|
||||||
|
|
||||||
|
def determine_db_record
|
||||||
|
if db_name == 'VHCS_HIPAA'
|
||||||
|
@db_record = 'VhcsRecord'.constantize
|
||||||
|
@module = 'Vhcs'.constantize
|
||||||
|
@file_folder = 'vhcs'
|
||||||
|
elsif db_name == 'Baclight'
|
||||||
|
@db_record = 'BaclightRecord'.constantize
|
||||||
|
@module = 'Baclight'.constantize
|
||||||
|
@file_folder = 'baclight'
|
||||||
|
elsif db_name == 'HEBWeb'
|
||||||
|
@db_record = 'HebWebRecord'.constantize
|
||||||
|
@module = 'HebWeb'.constantize
|
||||||
|
@file_folder = 'heb_web'
|
||||||
|
else
|
||||||
|
raise LegacyDBModelError, "Invalid database, please use VHCS_HIPAA or HEBWeb"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_fields_if_table_exists
|
||||||
|
tn = table_name.strip
|
||||||
|
if @db_record.connection.table_exists?(tn) || @db_record.connection.view_exists?(table_name)
|
||||||
|
@fields = @db_record.connection.columns(tn).map { |col| col.name }
|
||||||
|
else
|
||||||
|
raise LegacyDBModelError, "Invalid table/view for #{db_name}, please double check spelling and capitalization"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Add argurment for database, depending on db - change AR::Base below to correct db record
|
||||||
|
# Find out way to error out if table not found
|
||||||
|
|
||||||
|
|
||||||
|
# use this code to get table field names here, convert to underscore,
|
||||||
|
# send as aray or array pairs, and pass to template.
|
||||||
|
# columns = ActiveRecord::Base.connection.columns(table_name)
|
||||||
|
# field_names = columns.map(&:name)
|
||||||
|
|
||||||
|
def create_model_file
|
||||||
|
template "legacy_model.rb.erb.tt", "app/models/#{@file_folder}/#{file_name}.rb"
|
||||||
|
end
|
||||||
|
|
||||||
|
# kick off another schema dump and rebuild alt db schema
|
||||||
|
# Will need to persist what tables to run (maybe in db, maybe in alt db schema)
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def file_name
|
||||||
|
"#{table_name.underscore}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def class_name
|
||||||
|
"#{table_name.camelize}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def uddt_type_mapping
|
||||||
|
{
|
||||||
|
"uddtBaseKey" => "integer, limit: 8",
|
||||||
|
"uddtIdPlan" => "string",
|
||||||
|
"uddtBaseEnum" => "uddt_base_enum",
|
||||||
|
"uddtBaseShortDesc" => "string",
|
||||||
|
"uddtBaseLongDesc" => "string",
|
||||||
|
"uddtBaseContact" => "string",
|
||||||
|
"uddtAddrCity" => "string",
|
||||||
|
"uddtAddrState" => "string",
|
||||||
|
"uddtAddrStreet" => "string",
|
||||||
|
"uddtAddrZip" => "string",
|
||||||
|
"uddtContPhone" => "string",
|
||||||
|
"uddtIdTax" => "string",
|
||||||
|
"uddtNotesNote" => "string",
|
||||||
|
"uddtFooter" => "string",
|
||||||
|
"uddtBaseDate" => "datetime",
|
||||||
|
"uddtBaseUserId" => "string"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
class LegacyDBModelError < StandardError; end
|
||||||
|
end
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
module <%= @module %>
|
||||||
|
class <%= class_name %> < <%= @db_record %>
|
||||||
|
|
||||||
|
self.table_name = '<%= table_name %>'
|
||||||
|
|
||||||
|
<%- @fields.each do |fi| -%>
|
||||||
|
alias_attribute :<%= fi.underscore.gsub(/(?=\d)/, "_") %>, :<%= fi %>
|
||||||
|
<%- end -%>
|
||||||
|
|
||||||
|
<%- unless db_name == 'BrittonWeb || Baclight' -%>
|
||||||
|
def attributes
|
||||||
|
rails_like = {
|
||||||
|
<%- @fields.each do |fi| -%>
|
||||||
|
<%= fi.underscore.gsub(/(?=\d)/, "_") %>: self.<%= fi.underscore.gsub(/(?=\d)/, "_") %>,
|
||||||
|
<%- end -%>
|
||||||
|
}
|
||||||
|
super.merge(rails_like)
|
||||||
|
end
|
||||||
|
<%- end -%>
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -13,7 +13,7 @@ class JsonWebToken
|
|||||||
# encode payload into a jwt
|
# encode payload into a jwt
|
||||||
# payload is usually like: { user_id: 1 }
|
# payload is usually like: { user_id: 1 }
|
||||||
# exp = token expiration (default: 1 hour from now for better security)
|
# exp = token expiration (default: 1 hour from now for better security)
|
||||||
# jti = unique token identifier for blacklisting
|
# jti = unique token identifier for retireing
|
||||||
def self.encode(payload, exp = 1.hour.from_now)
|
def self.encode(payload, exp = 1.hour.from_now)
|
||||||
payload[:exp] = exp.to_i
|
payload[:exp] = exp.to_i
|
||||||
payload[:jti] ||= SecureRandom.uuid
|
payload[:jti] ||= SecureRandom.uuid
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
require "rails_helper"
|
|
||||||
|
|
||||||
RSpec.describe BlacklistedToken, type: :model do
|
|
||||||
let(:user) { User.create!(email: "test@example.com", password: "123456", password_confirmation: "123456") }
|
|
||||||
|
|
||||||
describe "validations" do
|
|
||||||
it "requires jti" do
|
|
||||||
token = BlacklistedToken.new(user: user, exp: 1.hour.from_now)
|
|
||||||
expect(token.valid?).to be false
|
|
||||||
expect(token.errors[:jti]).to include("can't be blank")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "requires exp" do
|
|
||||||
token = BlacklistedToken.new(user: user, jti: SecureRandom.uuid)
|
|
||||||
expect(token.valid?).to be false
|
|
||||||
expect(token.errors[:exp]).to include("can't be blank")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "requires unique jti" do
|
|
||||||
jti = SecureRandom.uuid
|
|
||||||
BlacklistedToken.create!(user: user, jti: jti, exp: 1.hour.from_now)
|
|
||||||
|
|
||||||
duplicate = BlacklistedToken.new(user: user, jti: jti, exp: 1.hour.from_now)
|
|
||||||
expect(duplicate.valid?).to be false
|
|
||||||
expect(duplicate.errors[:jti]).to include("has already been taken")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe ".blacklisted?" do
|
|
||||||
it "returns true for blacklisted tokens" do
|
|
||||||
jti = SecureRandom.uuid
|
|
||||||
BlacklistedToken.create!(user: user, jti: jti, exp: 1.hour.from_now)
|
|
||||||
|
|
||||||
expect(BlacklistedToken.blacklisted?(jti)).to be true
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns false for non-blacklisted tokens" do
|
|
||||||
expect(BlacklistedToken.blacklisted?("non-existent-jti")).to be false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe ".cleanup_expired" do
|
|
||||||
it "removes expired tokens" do
|
|
||||||
expired_token = BlacklistedToken.create!(user: user, jti: SecureRandom.uuid, exp: 1.day.ago)
|
|
||||||
valid_token = BlacklistedToken.create!(user: user, jti: SecureRandom.uuid, exp: 1.hour.from_now)
|
|
||||||
|
|
||||||
expect {
|
|
||||||
BlacklistedToken.cleanup_expired
|
|
||||||
}.to change { BlacklistedToken.count }.by(-1)
|
|
||||||
|
|
||||||
expect(BlacklistedToken.exists?(expired_token.id)).to be false
|
|
||||||
expect(BlacklistedToken.exists?(valid_token.id)).to be true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe RetiredToken, type: :model do
|
||||||
|
let(:user) { User.create!(email: "test@example.com", password: "123456", password_confirmation: "123456") }
|
||||||
|
|
||||||
|
describe "validations" do
|
||||||
|
it "requires jti" do
|
||||||
|
token = RetiredToken.new(user: user, exp: 1.hour.from_now)
|
||||||
|
expect(token.valid?).to be false
|
||||||
|
expect(token.errors[:jti]).to include("can't be blank")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "requires exp" do
|
||||||
|
token = RetiredToken.new(user: user, jti: SecureRandom.uuid)
|
||||||
|
expect(token.valid?).to be false
|
||||||
|
expect(token.errors[:exp]).to include("can't be blank")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "requires unique jti" do
|
||||||
|
jti = SecureRandom.uuid
|
||||||
|
RetiredToken.create!(user: user, jti: jti, exp: 1.hour.from_now)
|
||||||
|
|
||||||
|
duplicate = RetiredToken.new(user: user, jti: jti, exp: 1.hour.from_now)
|
||||||
|
expect(duplicate.valid?).to be false
|
||||||
|
expect(duplicate.errors[:jti]).to include("has already been taken")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".retired?" do
|
||||||
|
it "returns true for retired tokens" do
|
||||||
|
jti = SecureRandom.uuid
|
||||||
|
RetiredToken.create!(user: user, jti: jti, exp: 1.hour.from_now)
|
||||||
|
|
||||||
|
expect(RetiredToken.retired?(jti)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false for non-retired tokens" do
|
||||||
|
expect(RetiredToken.retired?("non-existent-jti")).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".cleanup_expired" do
|
||||||
|
it "removes expired tokens" do
|
||||||
|
expired_token = RetiredToken.create!(user: user, jti: SecureRandom.uuid, exp: 1.day.ago)
|
||||||
|
valid_token = RetiredToken.create!(user: user, jti: SecureRandom.uuid, exp: 1.hour.from_now)
|
||||||
|
|
||||||
|
expect {
|
||||||
|
RetiredToken.cleanup_expired
|
||||||
|
}.to change { RetiredToken.count }.by(-1)
|
||||||
|
|
||||||
|
expect(RetiredToken.exists?(expired_token.id)).to be false
|
||||||
|
expect(RetiredToken.exists?(valid_token.id)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,7 +2,7 @@ require "swagger_helper"
|
|||||||
|
|
||||||
RSpec.describe "api/v1/logout", type: :request do
|
RSpec.describe "api/v1/logout", type: :request do
|
||||||
path "/api/v1/logout" do
|
path "/api/v1/logout" do
|
||||||
post "logs out user and blacklists token" do
|
post "logs out user and retires token" do
|
||||||
tags "Auth"
|
tags "Auth"
|
||||||
consumes "application/json"
|
consumes "application/json"
|
||||||
produces "application/json"
|
produces "application/json"
|
||||||
@@ -20,7 +20,7 @@ RSpec.describe "api/v1/logout", type: :request do
|
|||||||
data = JSON.parse(response.body)
|
data = JSON.parse(response.body)
|
||||||
expect(data["message"]).to include("Successfully logged out")
|
expect(data["message"]).to include("Successfully logged out")
|
||||||
|
|
||||||
# verify token was blacklisted by checking the response
|
# verify token was retired by checking the response
|
||||||
# (we can't decode the token variable here as it's scoped to the let block)
|
# (we can't decode the token variable here as it's scoped to the let block)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -36,16 +36,16 @@ RSpec.describe "api/v1/logout", type: :request do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "blacklisted token rejection" do
|
describe "retired token rejection" do
|
||||||
it "rejects requests with blacklisted tokens" do
|
it "rejects requests with retired tokens" do
|
||||||
user = User.create!(email: "test@example.com", password: "123456", password_confirmation: "123456")
|
user = User.create!(email: "test@example.com", password: "123456", password_confirmation: "123456")
|
||||||
token = JsonWebToken.encode(user_id: user.id)
|
token = JsonWebToken.encode(user_id: user.id)
|
||||||
|
|
||||||
# first logout to blacklist the token
|
# first logout to retire the token
|
||||||
post "/api/v1/logout", headers: { "Authorization" => "Bearer #{token}" }
|
post "/api/v1/logout", headers: { "Authorization" => "Bearer #{token}" }
|
||||||
expect(response).to have_http_status(:ok)
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
# try to access protected endpoint with blacklisted token
|
# try to access protected endpoint with retired token
|
||||||
get "/api/v1/profile", headers: { "Authorization" => "Bearer #{token}" }
|
get "/api/v1/profile", headers: { "Authorization" => "Bearer #{token}" }
|
||||||
expect(response).to have_http_status(:unauthorized)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
data = JSON.parse(response.body)
|
data = JSON.parse(response.body)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ paths:
|
|||||||
example: '123456'
|
example: '123456'
|
||||||
"/api/v1/logout":
|
"/api/v1/logout":
|
||||||
post:
|
post:
|
||||||
summary: logs out user and blacklists token
|
summary: logs out user and retires token
|
||||||
tags:
|
tags:
|
||||||
- Auth
|
- Auth
|
||||||
security:
|
security:
|
||||||
|
|||||||
Reference in New Issue
Block a user