diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 95347033..c0872fcf 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -52,11 +52,17 @@ jobs:
bundle exec rails db:create
bundle exec rails db:migrate
- - name: Run tests
+ - name: Run Non-JS tests
env:
RAILS_ENV: test
DATABASE_URL: postgres://postgres:postgres@localhost:5432/access_pdf_test
- run: bundle exec rspec
+ run: bundle exec rspec spec/models spec/requests
+
+ - name: Run JS tests
+ env:
+ RAILS_ENV: test
+ DATABASE_URL: postgres://postgres:postgres@localhost:5432/access_pdf_test
+ run: bundle exec rspec spec/features
- name: Keep screenshots from failed system tests
uses: actions/upload-artifact@v4
diff --git a/.github/workflows/eval.yml b/.github/workflows/eval.yml
index a1949bdf..8c364ab1 100644
--- a/.github/workflows/eval.yml
+++ b/.github/workflows/eval.yml
@@ -14,14 +14,14 @@ on:
description: Inference Model
options:
- gemini-2.5-pro-preview-03-25
- - gemini-1.5-pro-latest
+ - gemini-2.0-flash
+ - gemini-2.0-flash-lite
evaluation_model_name:
type: choice
description: Evaluation Model
options:
- gemini-2.5-flash
- gemini-2.5-pro-preview-03-25
- - gemini-1.5-pro-latest
runs_per_document:
description: 'Number of times generate evaluations per document'
required: false
diff --git a/.github/workflows/python_components.yml b/.github/workflows/python_components.yml
index b97ba750..da4becc1 100644
--- a/.github/workflows/python_components.yml
+++ b/.github/workflows/python_components.yml
@@ -26,6 +26,7 @@ jobs:
python -m pip install --upgrade pip
if [ -f ./python_components/ci/requirements.txt ]; then pip install -r ./python_components/ci/requirements.txt; fi
pip install python_components/evaluation
+ pip install python_components/document_inference
python -m spacy download en_core_web_sm
- name: Run Linting
run: |
diff --git a/Gemfile b/Gemfile
index 451fcc96..800e5419 100644
--- a/Gemfile
+++ b/Gemfile
@@ -54,6 +54,7 @@ group :test do
gem "rails-controller-testing"
gem "factory_bot_rails", "~> 6.5"
gem "capybara"
+ gem "capybara-email"
gem "webdrivers"
end
@@ -62,6 +63,7 @@ gem "aws-sdk-s3", "~> 1.194" # For S3 versioning support
gem "aws-sdk-secretsmanager"
gem "aws-sdk-lambda"
gem "aws-sigv4"
+gem "aws-sdk-ses"
# API and Documentation
gem "grape", "~> 2.4"
diff --git a/Gemfile.lock b/Gemfile.lock
index 69f4fc7b..ff731c2d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -103,6 +103,9 @@ GEM
aws-sdk-secretsmanager (1.117.0)
aws-sdk-core (~> 3, >= 3.227.0)
aws-sigv4 (~> 1.5)
+ aws-sdk-ses (1.85.0)
+ aws-sdk-core (~> 3, >= 3.225.0)
+ aws-sigv4 (~> 1.5)
aws-sigv4 (1.12.1)
aws-eventstream (~> 1, >= 1.0.2)
base64 (0.3.0)
@@ -131,6 +134,9 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
+ capybara-email (3.0.2)
+ capybara (>= 2.4, < 4.0)
+ mail
cgi (0.5.0)
chartkick (5.2.0)
childprocess (5.1.0)
@@ -286,7 +292,11 @@ GEM
parser (3.3.8.0)
ast (~> 2.4.1)
racc
- pg (1.5.9)
+ pg (1.6.0)
+ pg (1.6.0-aarch64-linux)
+ pg (1.6.0-arm64-darwin)
+ pg (1.6.0-x86_64-darwin)
+ pg (1.6.0-x86_64-linux)
pp (0.6.2)
prettyprint
prettyprint (0.2.0)
@@ -518,6 +528,7 @@ DEPENDENCIES
aws-sdk-lambda
aws-sdk-s3 (~> 1.194)
aws-sdk-secretsmanager
+ aws-sdk-ses
aws-sigv4
bcrypt (~> 3.1)
better_errors (~> 2.10)
@@ -525,6 +536,7 @@ DEPENDENCIES
brakeman (~> 7.1)
bundler-audit (~> 0.9.2)
capybara
+ capybara-email
chartkick
cssbundling-rails (~> 1.4.3)
csv
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
new file mode 100644
index 00000000..929c7b6f
--- /dev/null
+++ b/app/controllers/admin/users_controller.rb
@@ -0,0 +1,83 @@
+class Admin::UsersController < ApplicationController
+ include Access
+
+ before_action :ensure_user_is_user_admin
+
+ before_action :set_user, only: [:edit, :update]
+ before_action :site_list, only: [:new, :create, :edit, :update]
+ before_action :set_minimum_password_length, only: [:new, :edit, :update]
+
+ def index
+ @users = User.all
+ end
+
+ def new
+ @user = User.new
+ render :new
+ end
+
+ def create
+ @user = User.new(user_params)
+ if @user.is_invited?
+ temp_password = SecureRandom.hex(12)
+ @user.password = temp_password
+ @user.password_confirmation = temp_password
+ end
+ if @user.save
+ begin
+ msg = "User added successfully"
+ if @user.send_new_account_instructions?
+ msg = "User added successfully. Instructions were emailed to the user."
+ end
+ redirect_to admin_users_path, notice: msg
+ rescue Net::SMTPFatalError => e
+ redirect_to admin_users_path, alert: e.message
+ end
+ else
+ render :new, status: 422
+ end
+ end
+
+ def edit
+ render :edit
+ end
+
+ def update
+ if params[:user][:password].blank?
+ params[:user].delete(:password)
+ params[:user].delete(:password_confirmation)
+ params[:user].delete(:current_password)
+ success = @user.update_without_password(user_params)
+ elsif @user.id == current_user.id
+ success = @user.update_with_password(user_params)
+ bypass_sign_in @user, scope: "user"
+ else
+ success = @user.update(user_params)
+ end
+ if success
+ redirect_to admin_users_path, notice: "User updated successfully"
+ else
+ render :edit, status: 422
+ end
+ end
+
+ private
+
+ def site_list
+ @sites = Site.all.order(:location, :name).group_by(&:location).map do |location, sites|
+ [location, sites.map { |site| [site.name, site.id] }]
+ end
+ end
+
+ def set_user
+ @user = User.find(params[:id])
+ end
+
+ def user_params
+ params.require(:user).permit(:email, :password, :password_confirmation, :current_password, :is_site_admin, :is_user_admin, :site_id, :is_invited)
+ end
+
+ def set_minimum_password_length
+ @minimum_password_length = User.password_length.min
+ end
+end
diff --git a/app/controllers/concerns/access.rb b/app/controllers/concerns/access.rb
index 07554a99..b6e0861c 100644
--- a/app/controllers/concerns/access.rb
+++ b/app/controllers/concerns/access.rb
@@ -1,18 +1,24 @@
module Access
- def ensure_user_admin
- unless current_user.is_admin?
+ def ensure_user_site_admin
+ unless current_user.present? && current_user.is_site_admin?
+ redirect_to sites_path, alert: "You don't have permission to access that page."
+ end
+ end
+
+ def ensure_user_is_user_admin
+ unless current_user.present? && current_user.is_user_admin?
redirect_to sites_path, alert: "You don't have permission to access that page."
end
end
def ensure_user_site_access
- if !current_user.is_admin? && (current_user.site.nil? || current_user.site.id != @site.id)
+ if current_user.nil? || !current_user.is_site_admin? && (current_user.site.nil? || current_user.site.id != @site.id)
redirect_to sites_path, alert: "You don't have permission to access that site."
end
end
def ensure_user_document_access
- if !current_user.is_admin? && (current_user.site.nil? || current_user.site.documents.find(@document.id).nil?)
+ if current_user.nil? || !current_user.is_site_admin? && (current_user.site.nil? || current_user.site.documents.find(@document.id).nil?)
redirect_to sites_path, alert: "You don't have permission to perform that action on document."
end
end
diff --git a/app/controllers/configurations_controller.rb b/app/controllers/configurations_controller.rb
index a516be67..bf13c4c5 100644
--- a/app/controllers/configurations_controller.rb
+++ b/app/controllers/configurations_controller.rb
@@ -1,7 +1,7 @@
class ConfigurationsController < AuthenticatedController
include Access
- before_action :ensure_user_admin
+ before_action :ensure_user_site_admin
ASAP_API_USER = "/asap-pdf/production/RAILS_API_USER-20250613220933079900000001"
ASAP_API_PASSWORD = "/asap-pdf/production/RAILS_API_PASSWORD-20250613220933080000000003"
diff --git a/app/controllers/sites_controller.rb b/app/controllers/sites_controller.rb
index 8aa19d16..fde11e78 100644
--- a/app/controllers/sites_controller.rb
+++ b/app/controllers/sites_controller.rb
@@ -6,7 +6,7 @@ class SitesController < AuthenticatedController
before_action :ensure_user_site_access, only: [:insights, :show, :edit, :update, :destroy]
def index
- @sites = if current_user.is_admin?
+ @sites = if current_user.is_site_admin?
Site.all
else
current_user.site.nil? ? [] : [current_user.site]
diff --git a/app/controllers/users/passwords_controller.rb b/app/controllers/users/passwords_controller.rb
index 9f2d9aee..ce8cd164 100644
--- a/app/controllers/users/passwords_controller.rb
+++ b/app/controllers/users/passwords_controller.rb
@@ -2,34 +2,18 @@
class Users::PasswordsController < Devise::PasswordsController
layout "centered"
- # GET /resource/password/new
- # def new
- # super
- # end
- # POST /resource/password
- # def create
- # super
- # end
+ def create
+ self.resource = resource_class.send_reset_password_instructions(resource_params)
+ set_flash_message! :notice, :send_paranoid_instructions
+ resource.email = nil
+ redirect_back(fallback_location: new_password_path(resource_name))
+ 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
+ def edit
+ @is_invitation = params[:is_invitation] == "1"
+ self.resource = resource_class.new
+ set_minimum_password_length
+ resource.reset_password_token = params[:reset_password_token]
+ end
end
diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb
index c266662e..0ddd28c2 100644
--- a/app/controllers/users/registrations_controller.rb
+++ b/app/controllers/users/registrations_controller.rb
@@ -1,63 +1,11 @@
# frozen_string_literal: true
class Users::RegistrationsController < Devise::RegistrationsController
- layout "centered"
- # before_action :configure_sign_up_params, only: [:create]
- # before_action :configure_account_update_params, only: [:update]
+ def new
+ redirect_to root_path
+ end
- # GET /resource/sign_up
- # def new
- # super
- # end
-
- # POST /resource
- # def create
- # super
- # 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
-
- # If you have extra params to permit, append them to the sanitizer.
- # def configure_sign_up_params
- # devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
- # 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
+ def create
+ redirect_to root_path
+ end
end
diff --git a/app/javascript/controllers/modal_controller.js b/app/javascript/controllers/modal_controller.js
index 8304a8d3..37554b8f 100644
--- a/app/javascript/controllers/modal_controller.js
+++ b/app/javascript/controllers/modal_controller.js
@@ -5,7 +5,9 @@ export default class extends Controller {
connect() {
super.connect();
- this.wrapperTarget.addEventListener('close', this.onModalClose.bind(this))
+ if (this.hasWrapperTarget) {
+ this.wrapperTarget.addEventListener('close', this.onModalClose.bind(this))
+ }
}
submitAndClose(event) {
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
index 3c34c814..710b5421 100644
--- a/app/mailers/application_mailer.rb
+++ b/app/mailers/application_mailer.rb
@@ -1,4 +1,11 @@
-class ApplicationMailer < ActionMailer::Base
- default from: "from@example.com"
+class ApplicationMailer < Devise::Mailer
+ default from: "Code for America Edit <%= @user.email %>
+
+ <%= form_with(model: [:admin, @user]) do |f| %>
+
+ Manage Users
+
+
+ Add User
+
+
+
+
+
+
+
+
+ <% @users.each do |user| %>
+ Email
+ Site
+ Site Admin
+ User Admin
+ Actions
+
+
+ <% end %>
+
+ <%= user.email %>
+ <%= user.site.present? ? user.site.name : "None" %>
+ <%= user.is_site_admin? ? "Yes" : "No" %>
+ <%= user.is_user_admin? ? "Yes" : "No" %>
+
+ <%= link_to "Edit", edit_admin_user_path(user), class: "text-primary" %>
+
+ Add New User
+
+ <%= form_for(@user, url: admin_users_path) do |f| %>
+
+ diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb index 3aac9002..d1ba3f75 100644 --- a/app/views/layouts/mailer.html.erb +++ b/app/views/layouts/mailer.html.erb @@ -1,13 +1,71 @@ - +
-