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
|
||||
|
||||
# Rails app lives here
|
||||
WORKDIR /rails
|
||||
WORKDIR app
|
||||
|
||||
# Install base packages
|
||||
RUN apt-get update -qq && \
|
||||
@@ -50,7 +50,7 @@ FROM base
|
||||
|
||||
# Copy built artifacts: gems, application
|
||||
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 groupadd --system --gid 1000 rails && \
|
||||
@@ -59,7 +59,7 @@ RUN groupadd --system --gid 1000 rails && \
|
||||
USER 1000:1000
|
||||
|
||||
# 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
|
||||
EXPOSE 3000
|
||||
|
||||
@@ -3,8 +3,6 @@ source "https://rubygems.org"
|
||||
ruby "3.3.1"
|
||||
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
|
||||
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]
|
||||
gem "puma", ">= 5.0"
|
||||
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
|
||||
@@ -45,7 +43,8 @@ end
|
||||
|
||||
gem "devise", "~> 4.9"
|
||||
|
||||
gem "jwt", "~> 2.10"
|
||||
# gem "jwt", "~> 2.10"
|
||||
gem 'devise-jwt'
|
||||
|
||||
gem "dotenv-rails", groups: [ :development, :test ]
|
||||
|
||||
@@ -54,3 +53,11 @@ gem "rspec-rails", "~> 8.0"
|
||||
gem "rswag", "~> 2.16"
|
||||
|
||||
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)
|
||||
activesupport (= 7.2.2.1)
|
||||
timeout (>= 0.4.0)
|
||||
activerecord-sqlserver-adapter (7.2.9)
|
||||
activerecord (~> 7.2.0)
|
||||
tiny_tds
|
||||
activestorage (7.2.2.1)
|
||||
actionpack (= 7.2.2.1)
|
||||
activejob (= 7.2.2.1)
|
||||
@@ -87,6 +90,7 @@ GEM
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.3)
|
||||
crass (1.0.6)
|
||||
csv (3.3.5)
|
||||
date (3.4.1)
|
||||
debug (1.10.0)
|
||||
irb (~> 1.10)
|
||||
@@ -97,16 +101,33 @@ GEM
|
||||
railties (>= 4.1.0)
|
||||
responders
|
||||
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)
|
||||
dotenv (3.1.8)
|
||||
dotenv-rails (3.1.8)
|
||||
dotenv (= 3.1.8)
|
||||
railties (>= 6.1)
|
||||
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)
|
||||
erubi (1.13.1)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
httparty (0.24.2)
|
||||
csv
|
||||
mini_mime (>= 1.0.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (1.14.7)
|
||||
concurrent-ruby (~> 1.0)
|
||||
io-console (0.8.0)
|
||||
@@ -118,7 +139,7 @@ GEM
|
||||
json-schema (5.1.1)
|
||||
addressable (~> 2.8)
|
||||
bigdecimal (~> 3.1)
|
||||
jwt (2.10.1)
|
||||
jwt (3.2.0)
|
||||
base64
|
||||
language_server-protocol (3.17.0.5)
|
||||
lint_roller (1.1.0)
|
||||
@@ -135,6 +156,8 @@ GEM
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.25.5)
|
||||
msgpack (1.8.0)
|
||||
multi_xml (0.9.1)
|
||||
bigdecimal (>= 3.1, < 5)
|
||||
net-imap (0.5.8)
|
||||
date
|
||||
net-protocol
|
||||
@@ -166,7 +189,6 @@ GEM
|
||||
parser (3.3.8.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pg (1.5.9)
|
||||
pp (0.6.2)
|
||||
prettyprint
|
||||
prettyprint (0.2.0)
|
||||
@@ -177,6 +199,8 @@ GEM
|
||||
public_suffix (6.0.2)
|
||||
puma (6.6.0)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.5.2)
|
||||
activesupport (>= 3.0.0)
|
||||
racc (1.8.1)
|
||||
rack (3.1.16)
|
||||
rack-attack (6.7.0)
|
||||
@@ -296,6 +320,16 @@ GEM
|
||||
stringio (3.1.7)
|
||||
thor (1.3.2)
|
||||
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)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (3.1.4)
|
||||
@@ -304,6 +338,11 @@ GEM
|
||||
useragent (0.16.11)
|
||||
warden (1.2.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)
|
||||
base64
|
||||
websocket-extensions (>= 0.1.0)
|
||||
@@ -323,20 +362,23 @@ PLATFORMS
|
||||
x86_64-linux-musl
|
||||
|
||||
DEPENDENCIES
|
||||
activerecord-sqlserver-adapter
|
||||
bootsnap
|
||||
brakeman
|
||||
debug
|
||||
devise (~> 4.9)
|
||||
devise-jwt
|
||||
dotenv-rails
|
||||
jwt (~> 2.10)
|
||||
pg (~> 1.1)
|
||||
httparty
|
||||
puma (>= 5.0)
|
||||
pundit
|
||||
rack-attack (~> 6.7)
|
||||
rack-cors
|
||||
rails (~> 7.2.2, >= 7.2.2.1)
|
||||
rspec-rails (~> 8.0)
|
||||
rswag (~> 2.16)
|
||||
rubocop-rails-omakase
|
||||
tiny_tds
|
||||
tzinfo-data
|
||||
|
||||
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>
|
||||
```
|
||||
|
||||
blacklists the current token (real logout, token becomes invalid)
|
||||
retires the current token (real logout, token becomes invalid)
|
||||
|
||||
## 👥 role-based authorization
|
||||
|
||||
@@ -146,7 +146,7 @@ end
|
||||
## 🔒 security features
|
||||
|
||||
* **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)
|
||||
* **rate limiting**: login, signup, and refresh endpoints are throttled
|
||||
* **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):
|
||||
|
||||
```ruby
|
||||
# clean up expired blacklisted tokens
|
||||
BlacklistedToken.cleanup_expired
|
||||
# clean up expired retired tokens
|
||||
RetiredToken.cleanup_expired
|
||||
|
||||
# clean up old refresh tokens
|
||||
RefreshToken.cleanup_old_tokens
|
||||
@@ -196,8 +196,8 @@ REDIS_URL=your_redis_url (optional, for rack-attack)
|
||||
### database indexes
|
||||
|
||||
migrations include proper indexes for performance:
|
||||
* `blacklisted_tokens.jti` (unique)
|
||||
* `blacklisted_tokens.exp`
|
||||
* `retired_tokens.jti` (unique)
|
||||
* `retired_tokens.exp`
|
||||
* `refresh_tokens.token` (unique)
|
||||
* `refresh_tokens.user_id + revoked`
|
||||
* `users.role`
|
||||
@@ -210,7 +210,7 @@ open issues or pull requests.
|
||||
## ✨ features
|
||||
|
||||
* ✅ 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)
|
||||
* ✅ Role-based authorization (user, moderator, admin roles)
|
||||
* ✅ Rate limiting with Rack::Attack (prevents brute force attacks)
|
||||
@@ -232,7 +232,7 @@ feel free to fork, star, share, or improve.
|
||||
|
||||
## ⚠️ 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:
|
||||
* review security settings for your specific use case
|
||||
* set up proper monitoring and logging
|
||||
|
||||
@@ -42,15 +42,15 @@ module Api
|
||||
end
|
||||
end
|
||||
|
||||
# post /logout -> NOW with real token blacklisting
|
||||
# adds the current token to blacklist so it can't be used again
|
||||
# post /logout -> NOW with real token retireing
|
||||
# adds the current token to retire so it can't be used again
|
||||
# authorize_request ensures @current_user and token are present
|
||||
def logout
|
||||
header = request.headers["Authorization"]
|
||||
token = header.split(" ").last if header
|
||||
|
||||
decoded = JsonWebToken.decode(token)
|
||||
BlacklistedToken.create!(
|
||||
RetiredToken.create!(
|
||||
jti: decoded[:jti],
|
||||
user_id: decoded[:user_id],
|
||||
exp: Time.at(decoded[:exp])
|
||||
@@ -59,7 +59,7 @@ module Api
|
||||
# also revoke all refresh tokens for this user
|
||||
@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
|
||||
render json: { error: "Logout failed: #{e.message}" }, status: :unprocessable_entity
|
||||
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
|
||||
include Pundit::Authorization
|
||||
end
|
||||
|
||||
@@ -19,8 +19,8 @@ module AuthorizeRequest
|
||||
begin
|
||||
decoded = JsonWebToken.decode(token)
|
||||
|
||||
# check if token is blacklisted (logged out)
|
||||
if BlacklistedToken.blacklisted?(decoded[:jti])
|
||||
# check if token is retired (logged out)
|
||||
if RetiredToken.retired?(decoded[:jti])
|
||||
render json: { error: "Token has been revoked" }, status: :unauthorized
|
||||
return
|
||||
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
|
||||
primary_abstract_class
|
||||
|
||||
establish_connection :primary
|
||||
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
|
||||
|
||||
validates :jti, presence: true, uniqueness: true
|
||||
validates :exp, presence: true
|
||||
|
||||
# check if a token is blacklisted
|
||||
def self.blacklisted?(jti)
|
||||
# check if a token is retired
|
||||
def self.retired?(jti)
|
||||
exists?(jti: jti)
|
||||
end
|
||||
|
||||
+127
-12
@@ -1,17 +1,132 @@
|
||||
class User < ApplicationRecord
|
||||
# Include default devise modules. Others available are:
|
||||
# :recoverable, :rememberable, :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
||||
devise :database_authenticatable, :registerable,
|
||||
:validatable
|
||||
include Devise::JWT::RevocationStrategies::JTIMatcher
|
||||
|
||||
# associations
|
||||
has_many :blacklisted_tokens, dependent: :destroy
|
||||
has_many :refresh_tokens, dependent: :destroy
|
||||
devise :database_authenticatable, :registerable,
|
||||
:validatable, :jwt_authenticatable, jwt_revocation_strategy: self
|
||||
# Include default devise modules. Others available are:
|
||||
# :recoverable, :rememberable, :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
|
||||
|
||||
# role-based authorization
|
||||
enum :role, { user: 0, admin: 1, moderator: 2 }
|
||||
# role-based authorization
|
||||
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
|
||||
|
||||
@@ -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
|
||||
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 "${@}"
|
||||
|
||||
Executable → Regular
@@ -41,5 +41,12 @@ module RailsApiAuthTemplate
|
||||
# Skip views, helpers and assets when generating a new resource.
|
||||
config.api_only = true
|
||||
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
|
||||
|
||||
+93
-76
@@ -1,85 +1,102 @@
|
||||
# PostgreSQL. Versions 9.3 and up are supported.
|
||||
#
|
||||
# Install the pg driver:
|
||||
# gem install pg
|
||||
# On macOS with Homebrew:
|
||||
# gem install pg -- --with-pg-config=/usr/local/bin/pg_config
|
||||
# On Windows:
|
||||
# gem install pg
|
||||
# Choose the win32 build.
|
||||
# Install PostgreSQL and put its /bin directory on your path.
|
||||
#
|
||||
# Configure Using Gemfile
|
||||
# gem "pg"
|
||||
#
|
||||
default: &default
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
# For details on connection pooling, see Rails configuration guide
|
||||
# https://guides.rubyonrails.org/configuring.html#database-pooling
|
||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
production:
|
||||
primary:
|
||||
adapter: sqlserver
|
||||
mode: dblib
|
||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
|
||||
host: 10.41.82.73
|
||||
port: 1433
|
||||
database: BrittonWeb
|
||||
username: BSTI
|
||||
password: BSTIBOY
|
||||
tds_version: 7.3
|
||||
|
||||
vhcs:
|
||||
adapter: sqlserver
|
||||
mode: dblib
|
||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
|
||||
host: "10.41.82.72"
|
||||
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:
|
||||
<<: *default
|
||||
database: rails_api_auth_template_development
|
||||
primary:
|
||||
adapter: sqlserver
|
||||
mode: dblib
|
||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
|
||||
# The specified database role being used to connect to PostgreSQL.
|
||||
# To create additional roles in PostgreSQL see `$ createuser --help`.
|
||||
# When left blank, PostgreSQL will use the default role. This is
|
||||
# the same name as the operating system user running Rails.
|
||||
#username: rails_api_auth_template
|
||||
host: 10.41.82.73
|
||||
port: 1433
|
||||
database: BrittonWeb
|
||||
username: BSTI
|
||||
password: BSTIBOY
|
||||
tds_version: 7.3
|
||||
|
||||
# The password associated with the PostgreSQL role (username).
|
||||
#password:
|
||||
vhcs:
|
||||
adapter: sqlserver
|
||||
mode: dblib
|
||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
|
||||
# Connect on a TCP socket. Omitted by default since the client uses a
|
||||
# domain socket that doesn't need configuration. Windows does not have
|
||||
# domain sockets, so uncomment these lines.
|
||||
#host: localhost
|
||||
host: "10.41.82.73"
|
||||
port: 1433
|
||||
database: VHCS_HIPAA
|
||||
username: BSTI
|
||||
password: BSTIBOY
|
||||
database_tasks: false
|
||||
tds_version: 7.3
|
||||
|
||||
# The TCP port the server listens on. Defaults to 5432.
|
||||
# If your server runs on a different port number, change accordingly.
|
||||
#port: 5432
|
||||
baclight:
|
||||
adapter: sqlserver
|
||||
mode: dblib
|
||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
|
||||
# Schema search path. The server defaults to $user,public
|
||||
#schema_search_path: myapp,sharedapp,public
|
||||
host: "10.41.82.73"
|
||||
port: 1433
|
||||
database: Baclight
|
||||
username: BSTI
|
||||
password: BSTIBOY
|
||||
database_tasks: false
|
||||
tds_version: 7.3
|
||||
|
||||
# Minimum log levels, in increasing order:
|
||||
# debug5, debug4, debug3, debug2, debug1,
|
||||
# log, notice, warning, error, fatal, and panic
|
||||
# Defaults to warning.
|
||||
#min_messages: notice
|
||||
|
||||
# Warning: The database defined as "test" will be erased and
|
||||
# re-generated from your development database when you run "rake".
|
||||
# Do not set this db to the same as development or production.
|
||||
test:
|
||||
<<: *default
|
||||
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"] %>
|
||||
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
|
||||
|
||||
@@ -17,6 +17,8 @@ Rails.application.configure do
|
||||
# Enable server timing.
|
||||
config.server_timing = true
|
||||
|
||||
config.hosts << "web_api"
|
||||
|
||||
# Enable/disable caching. By default caching is disabled.
|
||||
# Run rails dev:cache to toggle caching.
|
||||
if Rails.root.join("tmp/caching-dev.txt").exist?
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
|
||||
Rails.application.config.middleware.insert_before 0, Rack::Cors do
|
||||
allow do
|
||||
origins "*"
|
||||
origins 'http://localhost:3001'
|
||||
|
||||
resource "*",
|
||||
headers: :any,
|
||||
methods: [ :get, :post, :put, :patch, :delete, :options, :head ]
|
||||
methods: [ :get, :post, :put, :patch, :delete, :options, :head ],
|
||||
credentials: true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,6 +16,17 @@ Devise.setup do |config|
|
||||
# by default. You can change it below and use your own secret key.
|
||||
# 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
|
||||
# Configure the parent class to the devise controllers.
|
||||
# config.parent_controller = 'DeviseController'
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
# Use this to limit dissemination of sensitive information.
|
||||
# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
|
||||
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
|
||||
mount Rswag::Ui::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
|
||||
|
||||
# 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 :v1 do
|
||||
post "signup", to: "auth#signup"
|
||||
post "login", to: "auth#login"
|
||||
post "logout", to: "auth#logout"
|
||||
post "refresh", to: "auth#refresh"
|
||||
# post "signup", to: "auth#signup"
|
||||
# post "login", to: "auth#login"
|
||||
# post "logout", to: "auth#logout"
|
||||
# post "refresh", to: "auth#refresh"
|
||||
|
||||
get "protected", to: "protected#index"
|
||||
get "profile", to: "profile#show"
|
||||
# get "protected", to: "protected#index"
|
||||
# get "profile", to: "profile#show"
|
||||
|
||||
# admin routes
|
||||
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
|
||||
|
||||
@@ -7,6 +7,12 @@ class DeviseCreateUsers < ActiveRecord::Migration[7.2]
|
||||
t.string :email, 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
|
||||
t.string :reset_password_token
|
||||
t.datetime :reset_password_sent_at
|
||||
@@ -37,7 +43,8 @@ class DeviseCreateUsers < ActiveRecord::Migration[7.2]
|
||||
end
|
||||
|
||||
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, :unlock_token, unique: true
|
||||
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.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2026_01_28_000003) 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
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2025_06_13_174349) do
|
||||
create_table "users", force: :cascade do |t|
|
||||
t.string "email", 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.datetime "reset_password_sent_at"
|
||||
t.datetime "remember_created_at"
|
||||
t.datetime "created_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 ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||
t.index ["role"], name: "index_users_on_role"
|
||||
t.index ["jti"], name: "index_users_on_jti", unique: true
|
||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token"
|
||||
end
|
||||
|
||||
add_foreign_key "blacklisted_tokens", "users"
|
||||
add_foreign_key "refresh_tokens", "users"
|
||||
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
|
||||
# payload is usually like: { user_id: 1 }
|
||||
# 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)
|
||||
payload[:exp] = exp.to_i
|
||||
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
|
||||
path "/api/v1/logout" do
|
||||
post "logs out user and blacklists token" do
|
||||
post "logs out user and retires token" do
|
||||
tags "Auth"
|
||||
consumes "application/json"
|
||||
produces "application/json"
|
||||
@@ -20,7 +20,7 @@ RSpec.describe "api/v1/logout", type: :request do
|
||||
data = JSON.parse(response.body)
|
||||
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)
|
||||
end
|
||||
end
|
||||
@@ -36,16 +36,16 @@ RSpec.describe "api/v1/logout", type: :request do
|
||||
end
|
||||
end
|
||||
|
||||
describe "blacklisted token rejection" do
|
||||
it "rejects requests with blacklisted tokens" do
|
||||
describe "retired token rejection" do
|
||||
it "rejects requests with retired tokens" do
|
||||
user = User.create!(email: "test@example.com", password: "123456", password_confirmation: "123456")
|
||||
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}" }
|
||||
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}" }
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
data = JSON.parse(response.body)
|
||||
|
||||
@@ -47,7 +47,7 @@ paths:
|
||||
example: '123456'
|
||||
"/api/v1/logout":
|
||||
post:
|
||||
summary: logs out user and blacklists token
|
||||
summary: logs out user and retires token
|
||||
tags:
|
||||
- Auth
|
||||
security:
|
||||
|
||||
Reference in New Issue
Block a user