Production Build Working
CI / scan_ruby (push) Failing after 37s
CI / lint (push) Failing after 39s

This commit is contained in:
Jason Jordan
2026-09-16 12:41:44 -04:00
parent 9853d58469
commit 1c80e642f5
51 changed files with 648 additions and 440 deletions
+40 -39
View File
@@ -1,36 +1,39 @@
# syntax = docker/dockerfile:1
# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t my-app .
# docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY=<value from config/master.key> my-app
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
# 1. Base Stage: Common dependencies
ARG RUBY_VERSION=3.3.1
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
FROM ruby:$RUBY_VERSION-slim as base
# Rails app lives here
WORKDIR app
# Install base packages
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips postgresql-client && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Set production environment
WORKDIR /rails
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development"
BUNDLE_WITHOUT="development:test"
# Throw-away build stage to reduce size of final image
FROM base AS build
# Install packages needed to build gems
# Install base packages (libvips for Active Storage, curl for healthchecks)
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libpq-dev pkg-config && \
apt-get install --no-install-recommends -y \
build-essential \
curl \
git \
libvips \
freetds-bin \
freetds-dev \
libjemalloc2 \
ruby-dev \
dos2unix && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# 2. Build Stage: Gems and Assets
FROM base as build
# Install packages needed to build gems and precompile assets
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential pkg-config less && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Install application gems
RUN bundle config set --local frozen false
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
@@ -39,28 +42,26 @@ RUN bundle install && \
# Copy application code
COPY . .
# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/
RUN bundle exec bootsnap precompile app/
# Final stage for app image
# 3. Final Stage: Lean Runtime
FROM base
# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /app /app
# Copy built artifacts: gems and precompiled assets
# COPY --from=build /rails/public /rails/public
# COPY --from=build /rails/app/javascript /rails/app/javascript
# COPY --from=build /rails/config/importmap.rb /rails/config/importmap.rb
COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build /rails /rails
# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
chown -R rails:rails db log storage tmp
USER 1000:1000
# Run as a non-privileged user for security
RUN useradd -ms /bin/bash rails
RUN chown -R rails:rails /rails
USER rails:rails
# Entrypoint prepares the database.
ENTRYPOINT ["/app/bin/docker-entrypoint"]
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/rails", "server"]
# Start the server
EXPOSE 3005
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
+66
View File
@@ -0,0 +1,66 @@
# syntax = docker/dockerfile:1
# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t my-app .
# docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY=<value from config/master.key> my-app
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.3.1
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
# Rails app lives here
WORKDIR app
# Install base packages
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips postgresql-client && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Set production environment
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development"
# Throw-away build stage to reduce size of final image
FROM base AS build
# Install packages needed to build gems
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libpq-dev pkg-config && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
bundle exec bootsnap precompile --gemfile
# Copy application code
COPY . .
# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/
# Final stage for app image
FROM base
# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
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 && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
chown -R rails:rails db log storage tmp
USER 1000:1000
# Entrypoint prepares the database.
ENTRYPOINT ["/app/bin/docker-entrypoint"]
# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/rails", "server"]
@@ -1,23 +0,0 @@
module Api
module V1
class AdminController < ApplicationController
include AuthorizeRequest
include AuthorizeRole
before_action :require_admin
# example admin-only endpoint
# GET /api/v1/admin/dashboard
def dashboard
render json: {
message: "Welcome to admin dashboard",
stats: {
total_users: User.count,
total_admins: User.admin.count,
total_moderators: User.moderator.count
}
}, status: :ok
end
end
end
end
-87
View File
@@ -1,87 +0,0 @@
module Api
module V1
class AuthController < ApplicationController
include AuthorizeRequest
skip_before_action :authorize_request, only: %i[signup login refresh]
# post /signup -> signup user and return jwt + refresh token
# I used strong params so no sql injection here (rails got your back)
def signup
user = User.new(user_params)
if user.save
tokens = generate_tokens(user)
render json: { **tokens, user: user.as_json(only: %i[id email role]) }, status: :created
else
render json: { errors: user.errors.full_messages }, status: :unprocessable_entity
end
end
# post /login -> login with email & password (keep it simple)
# if it matches, it gives you both access token and refresh token
def login
user = User.find_by(email: params[:email])
if user&.valid_password?(params[:password])
tokens = generate_tokens(user)
render json: { **tokens, user: user.as_json(only: %i[id email role]) }, status: :ok
else
render json: { error: "Invalid email or password" }, status: :unauthorized
end
end
# post /refresh -> exchange refresh token for new access token
# keeps users logged in without re-entering credentials
def refresh
refresh_token = RefreshToken.find_by(token: params[:refresh_token])
if refresh_token&.active?
user = refresh_token.user
access_token = JsonWebToken.encode(user_id: user.id)
render json: { access_token:, user: user.as_json(only: %i[id email role]) }, status: :ok
else
render json: { error: "Invalid or expired refresh token" }, status: :unauthorized
end
end
# 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)
RetiredToken.create!(
jti: decoded[:jti],
user_id: decoded[:user_id],
exp: Time.at(decoded[:exp])
)
# also revoke all refresh tokens for this user
@current_user.refresh_tokens.update_all(revoked: true)
render json: { message: "Successfully logged out. Token retired." }, status: :ok
rescue StandardError => e
render json: { error: "Logout failed: #{e.message}" }, status: :unprocessable_entity
end
private
# only allow what we actually need. nothing fancy, nothing extra. not need for require
def user_params
params.permit(:email, :password, :password_confirmation)
end
# generate both access and refresh tokens
def generate_tokens(user)
access_token = JsonWebToken.encode(user_id: user.id)
refresh_token = user.refresh_tokens.create!
{
access_token: access_token,
refresh_token: refresh_token.token,
expires_in: 1.hour.to_i
}
end
end
end
end
+5 -3
View File
@@ -1,12 +1,14 @@
module Api
module V1
class BrokersController < ApplicationController
# before_action :authenticate_user!
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 }}
@broker = Baclight::Broker.find(broker_id)
authorize @broker
employers = @broker.baclight_employers.order(:name).map { |emp| {name: emp.name, entity_key: emp.pl_plan_key }}
render json: employers, status: :ok
end
@@ -3,6 +3,16 @@ module Api
class CarriersController < ApplicationController
before_action :authenticate_user!
def employers_list
carrier_id = params[:id]
@carrier = Baclight::Carrier.find(carrier_id)
authorize @carrier
employers = @carrier.baclight_employers.order(:name).map { |emp| {name: emp.name, entity_key: emp.pl_plan_key }}
render json: employers, status: :ok
end
end
end
end
@@ -1,18 +0,0 @@
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
+62 -8
View File
@@ -1,23 +1,26 @@
module Api
module V1
class EmployersController < ApplicationController
# before_action :authenticate_user!
before_action :authenticate_user!
def id_cards
pl_plan_key = params[:id]
id_cards = EmployersService::IdCards.new(pl_plan_key).call
@employer = Baclight::Employer.find_by(pl_plan_key: pl_plan_key)
authorize @employer
id_cards = EmployersService::IdCards.new(@employer.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'
response.headers['Access-Control-Expose-Headers'] = 'Content-Disposition'
send_data(
id_cards.body,
type: 'application/zip',
send_data id_cards.body,
filename: filename,
type: 'application/zip',
disposition: 'attachment'
)
else
render json: { error: "Employer not found" }, status: :not_found
end
@@ -25,12 +28,63 @@ module Api
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}}
@employer = Baclight::Employer.find_by(pl_plan_key: pl_plan_key)
authorize @employer
members = @employer.baclight_members.active.order(:name).map { |mem| {name: mem.name, entity_key: mem.pb_entity_key}}
render json: members, status: :ok
end
def coverage
pl_plan_key = params[:id]
@employer = Baclight::Employer.find_by(pl_plan_key: pl_plan_key)
authorize @employer
coverage = EmployersService::Coverage.new(@employer).call
if coverage.present?
render json: coverage, status: :ok
else
render json: { error: "Coverage not found" }, status: :not_found
end
end
def available_document_categories
pl_plan_key = params[:id]
@employer = Baclight::Employer.find_by(pl_plan_key: pl_plan_key)
authorize @employer
available_categories = Baclight::DocumentCategory.joins(:document_uploads).where(document_uploads: { pl_plan_key: pl_plan_key }).pluck(:id, :display_name).to_h
if available_categories.present?
render json: available_categories, status: :ok
else
render json: { }, status: :ok
end
end
def documents
pl_plan_key = params[:id]
category_id = params[:category_id]
@employer = Baclight::Employer.find_by(pl_plan_key: pl_plan_key)
authorize @employer
employer_document = Baclight::DocumentUpload.where(category_id: category_id, pl_plan_key: pl_plan_key).order(updated_at: :desc).first
if employer_document.present?
response.headers['Access-Control-Expose-Headers'] = 'Content-Disposition'
send_data employer_document.file_data,
filename: employer_document.filename,
type: employer_document.file_type,
disposition: "attachment"
else
render json: { error: "Document not found" }, status: :not_found
end
end
end
end
end
@@ -1,96 +0,0 @@
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
+17 -13
View File
@@ -1,7 +1,7 @@
module Api
module V1
class MembersController < ApplicationController
# before_action :authenticate_user!
before_action :authenticate_user!
def initialize_dashboard
pb_entity_key = params[:id]
@@ -17,7 +17,7 @@ module Api
pb_entity_key = params[:id]
layout = params[:layout]
@member = Baclight::Member.find_by(pb_entity_key: pb_entity_key)
# authorize @member
authorize @member
generated_id_card = MembersService::IdCard.new(@member, layout).call
@@ -37,21 +37,24 @@ module Api
end
end
def network_provider
pl_plan_key = params[:id]
# def network_provider
# pl_plan_key = params[:id]
network_provider = Baclight::IdCardSetup.find_by(pl_plan_key: pl_plan_key).slice(:network_provider)
# 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
# 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
@member = Baclight::Member.find_by(pb_entity_key: pb_entity_key)
authorize @member
plan_benefits = MembersService::PlanBenefits.new(@member).call
if plan_benefits.present?
render json: plan_benefits, status: :ok
@@ -60,9 +63,10 @@ module Api
end
end
def claims
def claims
pb_entity_key = params[:id]
@member = Baclight::Member.find_by(pb_entity_key: pb_entity_key)
authorize @member
recent_claims = MembersService::Claims.new(@member).call
@@ -1,19 +0,0 @@
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
@@ -1,16 +0,0 @@
module Api
module V1
class ProfileController < ApplicationController
include AuthorizeRequest
# get /profile
# this is for getting current user who logged in
def show
render json: {
id: @current_user.id,
email: @current_user.email
}, status: :ok
end
end
end
end
@@ -1,14 +0,0 @@
module Api
module V1
class ProtectedController < ApplicationController
include AuthorizeRequest
def index
render json: {
message: "you are free to use this buddy",
user: @current_user.as_json(only: %(id email))
}
end
end
end
end
@@ -3,6 +3,21 @@ module Api
class ProvidersController < ApplicationController
before_action :authenticate_user!
def claims
tax_id = params[:id]
member_pb_entity_key = params[:pb_entity_key]
@member = Baclight::Member.find_by(pb_entity_key: member_pb_entity_key)
if @member.present?
provider_claims = MembersService::Claims.new(@member, tax_id).call
else
provider_claims = nil
end
render json: provider_claims, status: :ok
end
end
end
end
@@ -0,0 +1,22 @@
module Api
module V1
class PublicController < ApplicationController
def search_eligibility
member_number = params[:member_number]
dob = params[:dob]
date = Date.parse(dob)
formatted_date = date.strftime("%m/%d/%Y")
member = Vhcs::VwmbMember.find_by(pb_entity_key: member_number, date_of_birth: formatted_date)
unless member.present?
member_number = nil
end
render json: member_number, status: :ok
end
end
end
end
+21 -16
View File
@@ -1,25 +1,30 @@
# frozen_string_literal: true
class Users::PasswordsController < Devise::PasswordsController
# GET /resource/password/new
# def new
# super
# end
# POST /users/password
def create
self.resource = resource_class.send_reset_password_instructions(resource_params)
yield resource if block_given?
# POST /resource/password
# def create
# super
# end
if successfully_sent?(resource)
render json: { message: "Password reset instructions sent successfully." }, status: :ok
else
render json: { errors: resource.errors.full_messages }, status: :unprocessable_entity
end
end
# GET /resource/password/edit?reset_password_token=abcdef
# def edit
# super
# end
# PUT /users/password
def update
self.resource = resource_class.reset_password_by_token(resource_params)
yield resource if block_given?
# PUT /resource/password
# def update
# super
# end
if resource.errors.empty?
resource.unlock_access! if unlockable?(resource)
render json: { message: "Password updated successfully." }, status: :ok
else
render json: { errors: resource.errors.full_messages }, status: :unprocessable_entity
end
end
# protected
+2 -2
View File
@@ -6,10 +6,10 @@ module Baclight
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
source: :baclight_employers
has_many :baclight_members,
through: :baclight_employers,
source: :baclight_member
source: :baclight_members
alias_attribute :id, :id
alias_attribute :name, :name
+15
View File
@@ -0,0 +1,15 @@
module Baclight
class DocumentCategory < BaclightRecord
self.table_name = 'document_categories'
alias_attribute :id, :id
alias_attribute :display_name, :display_name
alias_attribute :created_at, :created_at
alias_attribute :updated_at, :updated_at
has_many :document_uploads, class_name: "Baclight::DocumentUpload", foreign_key: "category_id", primary_key: "id"
end
end
+19
View File
@@ -0,0 +1,19 @@
module Baclight
class DocumentUpload < BaclightRecord
self.table_name = 'document_uploads'
alias_attribute :id, :id
alias_attribute :filename, :filename
alias_attribute :file_type, :file_type
alias_attribute :file_data, :file_data
alias_attribute :pl_plan_key, :pl_plan_key
alias_attribute :active, :active
alias_attribute :category_id, :category_id
alias_attribute :created_at, :created_at
alias_attribute :updated_at, :updated_at
belongs_to :document_category, class_name: "Baclight::DocumentUpload", foreign_key: "category_id"
end
end
+18 -13
View File
@@ -3,22 +3,27 @@ module Baclight
self.table_name = 'Employers'
has_one :baclight_carrier, through: :baclight_broker
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
# 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 carrier_id
self.baclight_broker.carrier_id
end
def belongs_to_levels
self.joins(baclight_broker: :baclight_carrier).select(
+8 -8
View File
@@ -5,14 +5,14 @@ module Baclight
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
# 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
+15 -1
View File
@@ -6,7 +6,7 @@ module Baclight
belongs_to :baclight_employer, class_name: "Baclight::Employer", foreign_key: "employer_id"
scope :active, -> {
where(active: true)
where(active: true)
}
# alias_attribute :id, :id
@@ -40,5 +40,19 @@ module Baclight
baclight_employer&.baclight_broker&.carrier_id
end
def eff_date
participation = Vhcs::PbProductParticipation.joins('INNER JOIN "PBCoveredEntities" ON "PBProductParticipation"."PBProductParticipationKey" = "PBCoveredEntities"."PBProductParticipationKey"').where('"PBCoveredEntities"."PBEntityKey" = ?', pb_entity_key).order(out_of_effect: :desc).first
eff_date = nil
if participation
in_effect = participation.in_effect
out_of_effect = participation.out_of_effect
if in_effect <= (Date.today + 90.days) && (out_of_effect - 1.day) > Date.today && out_of_effect > in_effect
eff_date = in_effect.strftime("%m/%d/%Y")
end
end
eff_date
end
end
end
+39 -42
View File
@@ -2,7 +2,7 @@ class User < ApplicationRecord
include Devise::JWT::RevocationStrategies::JTIMatcher
devise :database_authenticatable, :registerable,
:validatable, :jwt_authenticatable, jwt_revocation_strategy: self
:validatable, :recoverable, :jwt_authenticatable, jwt_revocation_strategy: self
# Include default devise modules. Others available are:
# :recoverable, :rememberable, :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
@@ -30,46 +30,34 @@ class User < ApplicationRecord
@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
role_entity.attributes.with_indifferent_access.slice(
:id, :name, :family_id, :mb_member_key, :pb_entity_key, :pl_plan_key, :employer_id
)
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'
role_entity.attributes.with_indifferent_access.slice(
:id, :name, :pl_plan_key, :company_pb_entity_key, :plan_id, :group_number
)
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 "provider"
{
id: self.id,
name: self.name,
tax_id: self.role_id
}
# if role_entity.present?
# role_entity.attributes.with_indifferent_access.slice(
# :id, :name, :pb_entity_key, :tax_id, :family_id
# )
# end
when "carrier"
if role_entity.present?
role_entity.attributes.with_indifferent_access.slice(
@@ -77,6 +65,11 @@ class User < ApplicationRecord
)
end
# when "admin"
# if role_entity.present?
# role_entity.attributes.with_indifferent_access.slice(
# :name, :id
# )
# end
end
end
@@ -91,33 +84,37 @@ class User < ApplicationRecord
find_initial_member
end
when "employer"
Baclight::Employer.find_by(company_pb_entity_key: role_id)
Baclight::Employer.find_by(pl_plan_key: role_id)
when "broker"
Baclight::Broker.find_by(broker_id: role_id)
Baclight::Broker.find_by(id: role_id)
when "provider"
Baclight::Provider.find_by(tax_id: role_id)
when "carrier"
Baclight::Carrier.find_by(carrier_id: role_id)
Baclight::Carrier.find_by(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
unless role == "provider"
if role == "member"
self.name = role_entity.name.split(",").reverse.join(" ").squish
else
self.name = role_entity.name
end
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.")
unless role == "provider"
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
end
@@ -0,0 +1,55 @@
module Vhcs
class PbProductParticipation < VhcsRecord
self.table_name = 'PbProductParticipation'
alias_attribute :pb_product_participation_key, :PBProductParticipationKey
alias_attribute :pb_affiliation_key, :PBAffiliationKey
alias_attribute :pb_product_availability_key, :PBProductAvailabilityKey
alias_attribute :coverage_type_code, :CoverageTypeCode
alias_attribute :optional_amount, :OptionalAmount
alias_attribute :in_effect, :InEffect
alias_attribute :out_of_effect, :OutOfEffect
alias_attribute :in_effect_reason_code, :InEffectReasonCode
alias_attribute :out_of_effect_reason_code, :OutOfEffectReasonCode
alias_attribute :when_last_changed, :WhenLastChanged
alias_attribute :who_last_changed, :WhoLastChanged
alias_attribute :user_defined_rate_criteria_value_1, :UserDefinedRateCriteriaValue1
alias_attribute :user_defined_rate_criteria_value_2, :UserDefinedRateCriteriaValue2
alias_attribute :user_defined_rate_criteria_value_3, :UserDefinedRateCriteriaValue3
alias_attribute :user_defined_rate_criteria_record_id_1, :UserDefinedRateCriteriaRecordID1
alias_attribute :user_defined_rate_criteria_record_id_2, :UserDefinedRateCriteriaRecordID2
alias_attribute :user_defined_rate_criteria_record_id_3, :UserDefinedRateCriteriaRecordID3
alias_attribute :optional_amount_record_id, :OptionalAmountRecordID
alias_attribute :primary_pb_affiliation_key, :PrimaryPBAffiliationKey
alias_attribute :payment_type_record_id_g_1_7_7, :PaymentTypeRecordID_G177
def attributes
rails_like = {
pb_product_participation_key: self.pb_product_participation_key,
pb_affiliation_key: self.pb_affiliation_key,
pb_product_availability_key: self.pb_product_availability_key,
coverage_type_code: self.coverage_type_code,
optional_amount: self.optional_amount,
in_effect: self.in_effect,
out_of_effect: self.out_of_effect,
in_effect_reason_code: self.in_effect_reason_code,
out_of_effect_reason_code: self.out_of_effect_reason_code,
when_last_changed: self.when_last_changed,
who_last_changed: self.who_last_changed,
user_defined_rate_criteria_value_1: self.user_defined_rate_criteria_value_1,
user_defined_rate_criteria_value_2: self.user_defined_rate_criteria_value_2,
user_defined_rate_criteria_value_3: self.user_defined_rate_criteria_value_3,
user_defined_rate_criteria_record_id_1: self.user_defined_rate_criteria_record_id_1,
user_defined_rate_criteria_record_id_2: self.user_defined_rate_criteria_record_id_2,
user_defined_rate_criteria_record_id_3: self.user_defined_rate_criteria_record_id_3,
optional_amount_record_id: self.optional_amount_record_id,
primary_pb_affiliation_key: self.primary_pb_affiliation_key,
payment_type_record_id_g_1_7_7: self.payment_type_record_id_g_1_7_7,
}
super.merge(rails_like)
end
end
end
+2 -2
View File
@@ -1,12 +1,12 @@
class Baclight::BrokerPolicy < ApplicationPolicy
def employers_list?; true; end
def employers_list?; broker_rule?; end
private
def broker_rule?
user.admin? ||
(user.broker? && record.broker_id == user.keychain[:id]) ||
(user.broker? && record.id == user.keychain[:id]) ||
(user.carrier? && record.carrier_id == user.keychain[:id])
end
+12
View File
@@ -0,0 +1,12 @@
class Baclight::CarrierPolicy < ApplicationPolicy
def employers_list?; carrier_rule?; end
private
def carrier_rule?
user.admin? ||
(user.carrier? && record.id == user.keychain[:id])
end
end
+13 -2
View File
@@ -1,13 +1,24 @@
class Baclight::EmployerPolicy < ApplicationPolicy
def id_cards?; employer_rule?; end
def members_list?; true; end
def members_list?; employer_rule?; end
def coverage?; employer_rule?; end
def available_document_categories?; member_employer_rule?; end
def documents?; member_employer_rule?; end
private
def member_employer_rule?
user.admin? ||
(user.member? && record.id == user.keychain[:employer_id]) ||
(user.employer? && record.id == user.keychain[:id]) ||
(user.broker? && record.broker_id == user.keychain[:id]) ||
(user.carrier? && record.carrier_id == user.keychain[:id])
end
def employer_rule?
user.admin? ||
(user.employer? && record.employer_id == user.keychain[:id]) ||
(user.employer? && record.id == user.keychain[:id]) ||
(user.broker? && record.broker_id == user.keychain[:id]) ||
(user.carrier? && record.carrier_id == user.keychain[:id])
end
+2 -1
View File
@@ -2,6 +2,7 @@ class Baclight::MemberPolicy < ApplicationPolicy
def initialize_dashboard?; member_rule?; end
def id_card?; member_rule?; end
def plan_benefits?; member_rule?; end
def claims?
user.admin? ||
@@ -12,7 +13,7 @@ class Baclight::MemberPolicy < ApplicationPolicy
private
def member_rule?
user.admin? ||
user.admin? || user.provider? ||
(user.member? && record.id == user.keychain[:id]) ||
(user.employer? && record.employer_id == user.keychain[:id]) ||
(user.broker? && record.broker_id == user.keychain[:id]) ||
+11
View File
@@ -0,0 +1,11 @@
class Baclight::ProviderPolicy < ApplicationPolicy
def claims?; provider_rule?; end
private
def provider_rule?
user.admin? || user.provider?
end
end
@@ -0,0 +1,29 @@
module EmployersService
class Coverage
def initialize(employer)
@employer = employer
end
def call
employer_plans = Baclight::IdCardPlan.where(pl_plan_key: @employer.pl_plan_key).to_a
employer_members = @employer.baclight_members.active.order(:name).to_a
employer_members.map! do |member|
eff_date = member.eff_date
if eff_date
plan_title = employer_plans.select { |plan| plan.id == member.id_card_plan_id }&.first&.title
{
name: member.name,
pb_entity_key: member.pb_entity_key,
plan_title: plan_title,
effective_date: eff_date,
dependents: JSON.parse(member.dependents)
}
else
nil
end
end
employer_members.compact
end
end
end
+3 -2
View File
@@ -1,7 +1,7 @@
module MembersService
class Claims
def initialize(entity, recent = false)
def initialize(entity, provider_tax_id = "", recent = false)
if entity.is_a?(Integer)
@member = Baclight::Member.find_by(pb_entity_key: entity)
else
@@ -10,6 +10,7 @@ module MembersService
if @member.present?
@pl_plan_key = @member.pl_plan_key
@family_id = @member.family_id
@provider_tax_id = provider_tax_id
@recent = recent
else
raise ArgumentError, "Member not found."
@@ -21,7 +22,7 @@ module MembersService
args = {
PLPlanKey: @pl_plan_key,
FamilyId: @family_id,
ProviderTaxId: "",
ProviderTaxId: @provider_tax_id,
UserID: "JASONJ"
}
claims_headers = CallStoredProc.new("CPFindClaim", args).call
@@ -12,7 +12,7 @@ module MembersService
def call
plan_benefits = MembersService::PlanBenefits.new(@member).call
network_provider = MembersService::NetworkProvider.new(@member).call
recent_claims = MembersService::Claims.new(@member, true).call
recent_claims = MembersService::Claims.new(@member, "", true).call
{
network_provider: network_provider,
@@ -11,10 +11,16 @@ module MembersService
end
def call
Baclight::IdCardPlan.joins(:baclight_id_card_plan_benefits)
plan_details = 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
{
plan_details: plan_details,
coverage_type: @member.coverage_class,
effective_date: @member.eff_date,
dependents: JSON.parse(@member.dependents)
}
end
end
end
@@ -0,0 +1,5 @@
<p>Welcome <%= @email %>!</p>
<p>You can confirm your account email through the link below:</p>
<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>
@@ -0,0 +1,7 @@
<p>Hello <%= @email %>!</p>
<% if @resource.try(:unconfirmed_email?) %>
<p>We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.</p>
<% else %>
<p>We're contacting you to notify you that your email has been changed to <%= @resource.email %>.</p>
<% end %>
@@ -0,0 +1,3 @@
<p>Hello <%= @resource.email %>!</p>
<p>We're contacting you to notify you that your password has been changed.</p>
@@ -0,0 +1,8 @@
<p>Hello <%= @resource.email %>!</p>
<p>Someone has requested a link to change your password. You can do this through the link below.</p>
<p><%= link_to 'Change my password', "http://localhost:3001/account/password-reset?token=#{@token}" %></p>
<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>
@@ -0,0 +1,7 @@
<p>Hello <%= @resource.email %>!</p>
<p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>
<p>Click the link below to unlock your account:</p>
<p><%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %></p>
@@ -0,0 +1,15 @@
<% if resource.errors.any? %>
<div id="error_explanation" data-turbo-cache="false">
<h2>
<%= I18n.t("errors.messages.not_saved",
count: resource.errors.count,
resource: resource.class.model_name.human.downcase)
%>
</h2>
<ul>
<% resource.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
+25
View File
@@ -0,0 +1,25 @@
<%- if controller_name != 'sessions' %>
<%= link_to "Log in", new_session_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to "Sign up", new_registration_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to "Forgot your password?", new_password_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br />
<% end %>
<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %><br />
<% end %>
<% end %>
+4 -3
View File
@@ -5,9 +5,10 @@ if [ -z "${LD_PRELOAD+x}" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then
export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)"
fi
# If running the rails server then create or migrate existing database
if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then
./bin/rails db:prepare
# If running the rails server then migrate existing database
if [ "${1}" == "bundle" ] && [ "${2}" == "exec" ] && [ "${3}" == "puma" ]; then
echo "running migrations..."
./bin/rails db:migrate
fi
rm -f /usr/src/app/tmp/pids/server.pid
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash -e
set -e
# Enable jemalloc for reduced memory usage and latency.
if [ -z "${LD_PRELOAD+x}" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then
export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)"
fi
# This prevents "A server is already running" errors if a previous container crashed.
if [ -f tmp/pids/server.pid ]; then
rm -f tmp/pids/server.pid
fi
rm -rf /usr/local/bundle/cache/*.gem
echo "bundle install..."
bundle check || bundle install --jobs 4
# NOTE: Enable this as you need to get migrations working in your deployed
# (i.e. Production) environments. Match the condition check to what you have in
# the Dockerfile CMD. I use puma in the production.Dockerfile as that's the
# recommended option by the Puma team (https://github.com/puma/puma#rails), but
# the default Rails Dockerfile uses `./bin/rails server`
echo "starting puma server..."
# If running the rails server then create or migrate existing database
if [ "${1}" == "bundle" ] && [ "${2}" == "exec" ] && [ "${3}" == "puma" ]; then
./bin/rails db:migrate
fi
exec "${@}"
+1 -1
View File
@@ -26,7 +26,7 @@ module RailsApiAuthTemplate
# Please, add to the `ignore` list any other `lib` subdirectories that do
# not contain `.rb` files, or that should not be reloaded or eager loaded.
# Common ones are `templates`, `generators`, or `middleware`, for example.
config.autoload_lib(ignore: %w[assets tasks])
config.autoload_lib(ignore: %w[assets tasks generators])
# Configuration for the application, engines, and railties goes here.
#
+1 -1
View File
@@ -40,7 +40,7 @@ Rails.application.configure do
# caching is enabled.
config.action_mailer.perform_caching = false
config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
config.action_mailer.default_url_options = { host: "localhost", port: 3002 }
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
+1 -1
View File
@@ -42,7 +42,7 @@ Rails.application.configure do
# config.assume_ssl = true
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true
config.force_ssl = false
# Skip http-to-https redirect for the default health check endpoint.
# config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }
+2 -2
View File
@@ -19,10 +19,10 @@ Devise.setup do |config|
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$}]
['POST', %r{^/signin$}]
]
jwt.revocation_requests = [
['DELETE', %r{^/logout$}]
['DELETE', %r{^/signout$}]
]
jwt.expiration_time = 30.minutes.to_i
end
+1 -1
View File
@@ -23,7 +23,7 @@ class Rack::Attack
end
# why not tell them politely if they try too much
self.throttled_response = lambda do |_env|
self.throttled_responder = lambda do |_env|
[
429,
{ "Content-Type" => "application/json" },
+1 -1
View File
@@ -24,7 +24,7 @@ threads_count = ENV.fetch("RAILS_MAX_THREADS", 3)
threads threads_count, threads_count
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
port ENV.fetch("PORT", 3000)
port ENV.fetch("PORT", 3005)
# Allow puma to be restarted by `bin/rails restart` command.
plugin :tmp_restart
+23 -2
View File
@@ -6,11 +6,13 @@ Rails.application.routes.draw do
path_names: {
sign_in: 'signin',
sign_out: 'signout',
registration: 'signup'
registration: 'signup',
password: 'forgot_password'
},
controllers: {
sessions: 'users/sessions',
registrations: 'users/registrations'
registrations: 'users/registrations',
passwords: 'users/passwords'
}
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
@@ -44,6 +46,7 @@ Rails.application.routes.draw do
get 'initialize_dashboard'
get 'id_card/:layout', to: 'members#id_card'
get 'claims'
get 'plan_benefits'
end
end
resources :employers do
@@ -51,6 +54,9 @@ Rails.application.routes.draw do
get 'initialize_dashboard'
get 'id_cards'
get 'members_list'
get 'coverage'
get 'available_document_categories'
get 'documents/:category_id', to: 'employers#documents'
end
end
resources :brokers do
@@ -58,6 +64,21 @@ Rails.application.routes.draw do
get 'employers_list'
end
end
resources :carriers do
member do
get 'employers_list'
end
end
resources :providers do
member do
get 'claims/:pb_entity_key', to: 'providers#claims'
end
end
resources :public do
collection do
get 'search_eligibility'
end
end
end
end
end
+2
View File
@@ -3,6 +3,7 @@ services:
build:
context: ./
dockerfile: development.Dockerfile
# dockerfile: Dockerfile
container_name: web_api
volumes:
- .:/usr/src/app
@@ -12,6 +13,7 @@ services:
environment:
- HISTFILE=/usr/src/app/log/.bash_history
- RAILS_ENV=development
# - RAILS_ENV=production
- BACLIGHT_SERVER_HOST=baclight
- BACLIGHT_SERVER_PORT=3002
- PUID=1000
+1 -1
View File
@@ -8,7 +8,7 @@ class JsonWebToken
# secret key used to sign the token
# make sure to keep this secret in .env (never hardcode and check before pushing to github)
# will crash if JWT_SECRET_KEY is not set - this is intentional for security
SECRET_KEY = ENV.fetch("JWT_SECRET_KEY")
SECRET_KEY = ENV.fetch("DEVISE_JWT_SECRET_KEY")
# encode payload into a jwt
# payload is usually like: { user_id: 1 }