Merge pull request #378 from Freika/feature/user-management

Basic user management
This commit is contained in:
Evgenii Burmakin 2024-11-12 15:09:08 +01:00 committed by GitHub
commit bb9fc2d97b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 168 additions and 31 deletions

View file

@ -1 +1 @@
0.16.3
0.16.4

View file

@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
# 0.16.4 - 2024-11-12
### Added
- Admins can now see all users in the system on the Users page. The path is `/settings/users`.
### Changed
- Admins can now provide custom password for new users and update passwords for existing users on the Users page.
# 0.16.3 - 2024-11-10
### Fixed

File diff suppressed because one or more lines are too long

View file

@ -14,7 +14,7 @@ class ApplicationController < ActionController::Base
end
def authenticate_admin!
return if current_user.admin?
return if current_user&.admin?
redirect_to root_path, notice: 'You are not authorized to perform this action.', status: :see_other
end

View file

@ -4,24 +4,51 @@ class Settings::UsersController < ApplicationController
before_action :authenticate_user!
before_action :authenticate_admin!
def index
@users = User.order(created_at: :desc)
end
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update(user_params)
redirect_to settings_users_url, notice: 'User was successfully updated.'
else
redirect_to settings_users_url, notice: 'User could not be updated.', status: :unprocessable_entity
end
end
def create
@user = User.new(
email: user_params[:email],
password: 'password',
password_confirmation: 'password'
password: user_params[:password],
password_confirmation: user_params[:password]
)
if @user.save
redirect_to settings_url,
notice: "User was successfully created, email is #{@user.email}, password is \"password\"."
redirect_to settings_users_url, notice: 'User was successfully created'
else
redirect_to settings_url, notice: 'User could not be created.', status: :unprocessable_entity
redirect_to settings_users_url, notice: 'User could not be created.', status: :unprocessable_entity
end
end
def destroy
@user = User.find(params[:id])
if @user.destroy
redirect_to settings_url, notice: 'User was successfully deleted.'
else
redirect_to settings_url, notice: 'User could not be deleted.', status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:email)
params.require(:user).permit(:email, :password)
end
end

View file

@ -1,4 +1,7 @@
<div role="tablist" class="tabs tabs-lifted tabs-lg">
<%= link_to 'Main', settings_path, role: 'tab', class: "tab #{active_tab?(settings_path)}" %>
<% if current_user.admin? %>
<%= link_to 'Users', settings_users_path, role: 'tab', class: "tab #{active_tab?(settings_users_path)}" %>
<%= link_to 'Background Jobs', settings_background_jobs_path, role: 'tab', class: "tab #{active_tab?(settings_background_jobs_path)}" %>
<% end %>
</div>

View file

@ -20,19 +20,5 @@
</div>
<% end %>
</div>
<div class="card flex-shrink-0 w-full max-w-sm shadow-2xl bg-base-100 px-5 py-5">
<h2 class="text-2xl font-bold">Create a new user!</h1>
<%= form_for :user, url: settings_users_path, method: :post, data: { turbo_method: :post, turbo: false } do |f| %>
<div class="form-control">
<%= f.label :email do %>
Email
<% end %>
<%= f.email_field :email, value: '', class: "input input-bordered" %>
</div>
<div class="form-control mt-5">
<%= f.submit "Create", class: "btn btn-primary" %>
</div>
<% end %>
</div>
</div>
</div>

View file

@ -0,0 +1,28 @@
<% content_for :title, 'Editing user' %>
<div class="min-h-content w-full">
<%= render 'settings/navigation' %>
<div class="flex w-full my-10 space-x-4">
<div class="overflow-x-auto w-4/12 mx-auto">
<h1 class="text-2xl font-bold">Editing user</h1>
<%= form_for @user, url: settings_user_path(@user), method: :put, data: { turbo_method: :put, turbo: false } do |f| %>
<div class="form-control">
<%= f.label :email do %>
Email
<% end %>
<%= f.email_field :email, value: @user.email, class: "input input-bordered" %>
</div>
<div class="form-control">
<%= f.label :password do %>
Password
<% end %>
<%= f.password_field :password, autofocus: true, autocomplete: "new-password", class: "input input-bordered" %>
</div>
<div class="form-control mt-5">
<%= f.submit "Update", class: "btn btn-primary" %>
</div>
<% end %>
</div>
</div>
</div>

View file

@ -0,0 +1,63 @@
<% content_for :title, 'Users' %>
<div class="min-h-content w-full">
<%= render 'settings/navigation' %>
<div class="flex flex-col lg:flex-row w-full my-10 space-x-4">
<div class="overflow-x-auto w-10/12 mx-auto">
<button class="btn" onclick="create_user.showModal()">Add new user</button>
<table class="table w-full">
<thead>
<tr>
<th>Email</th>
<th>Points</th>
<th>Created at</th>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td>
<div>
<%= link_to user.email, edit_settings_user_path(user), class: 'font-bold underline hover:no-underline' %>
</div>
</td>
<td>
<%= number_with_delimiter user.tracked_points.count %>
</td>
<td>
<%= user.created_at.strftime('%Y-%m-%d %H:%M:%S') %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
<dialog id="create_user" class="modal">
<div class="modal-box">
<h2 class="text-2xl font-bold">Create a new user!</h1>
<%= form_for :user, url: settings_users_path, method: :post, data: { turbo_method: :post, turbo: false } do |f| %>
<div class="form-control">
<%= f.label :email do %>
Email
<% end %>
<%= f.email_field :email, value: '', class: "input input-bordered" %>
</div>
<div class="form-control">
<%= f.label :password do %>
Password
<% end %>
<%= f.password_field :password, autofocus: true, autocomplete: "new-password", class: "input input-bordered" %>
</div>
<div class="form-control mt-5">
<%= f.submit "Create", class: "btn btn-primary" %>
</div>
<% end %>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>

View file

@ -19,7 +19,7 @@ Rails.application.routes.draw do
resources :settings, only: :index
namespace :settings do
resources :background_jobs, only: %i[index create destroy]
resources :users, only: :create
resources :users, only: %i[index create destroy edit update]
end
patch 'settings', to: 'settings#update'

View file

@ -3,7 +3,8 @@
require 'rails_helper'
RSpec.describe '/settings/users', type: :request do
let(:valid_attributes) { { email: 'user@domain.com' } }
let(:valid_attributes) { { email: 'user@domain.com', password: '4815162342' } }
let!(:admin) { create(:user, :admin) }
context 'when user is not authenticated' do
it 'redirects to sign in page' do
@ -25,8 +26,6 @@ RSpec.describe '/settings/users', type: :request do
end
context 'when user is an admin' do
let!(:admin) { create(:user, :admin) }
describe 'POST /create' do
before { sign_in admin }
@ -35,13 +34,16 @@ RSpec.describe '/settings/users', type: :request do
expect do
post settings_users_url, params: { user: valid_attributes }
end.to change(User, :count).by(1)
expect(User.last.email).to eq(valid_attributes[:email])
expect(User.last.valid_password?(valid_attributes[:password])).to be_truthy
end
it 'redirects to the created settings_user' do
post settings_users_url, params: { user: valid_attributes }
expect(response).to redirect_to(settings_url)
expect(flash[:notice]).to eq("User was successfully created, email is #{valid_attributes[:email]}, password is \"password\".")
expect(response).to redirect_to(settings_users_url)
expect(flash[:notice]).to eq('User was successfully created')
end
end
@ -61,6 +63,24 @@ RSpec.describe '/settings/users', type: :request do
end
end
end
describe 'PATCH /update' do
let(:user) { create(:user) }
before { sign_in admin }
context 'with valid parameters' do
let(:new_attributes) { { email: FFaker::Internet.email, password: '4815162342' } }
it 'updates the requested user' do
patch settings_user_url(user), params: { user: new_attributes }
user.reload
expect(user.email).to eq(new_attributes[:email])
expect(user.valid_password?(new_attributes[:password])).to be_truthy
end
end
end
end
end
end