Added users support
This commit is contained in:
118
README.md
118
README.md
@@ -1,24 +1,116 @@
|
|||||||
# README
|
# ZSTerminator -
|
||||||
|
|
||||||
This README would normally document whatever steps are necessary to get the
|
|
||||||
application up and running.
|
|
||||||
|
|
||||||
Things you may want to cover:
|
## Setup
|
||||||
|
|
||||||
* Ruby version
|
* Ruby version: 3.2.4
|
||||||
|
* Rails version: 7.1.5+
|
||||||
|
* Database: SQLite3
|
||||||
|
|
||||||
* System dependencies
|
## Getting Started
|
||||||
|
|
||||||
* Configuration
|
1. Install dependencies:
|
||||||
|
```bash
|
||||||
|
bundle install
|
||||||
|
```
|
||||||
|
|
||||||
* Database creation
|
2. Setup database:
|
||||||
|
```bash
|
||||||
|
rails db:create
|
||||||
|
rails db:migrate
|
||||||
|
rails db:seed
|
||||||
|
```
|
||||||
|
|
||||||
* Database initialization
|
3. Start the server:
|
||||||
|
```bash
|
||||||
|
rails server
|
||||||
|
```
|
||||||
|
|
||||||
* How to run the test suite
|
4. Visit http://localhost:3000 and login with:
|
||||||
|
- Username: `admin`
|
||||||
|
- Password: `password123`
|
||||||
|
|
||||||
* Services (job queues, cache servers, search engines, etc.)
|
## User Management (Rake Tasks)
|
||||||
|
|
||||||
* Deployment instructions
|
This project includes Rake tasks for managing users via command line:
|
||||||
|
|
||||||
* ...
|
### Available Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all users
|
||||||
|
rails users:list
|
||||||
|
|
||||||
|
# Show user details
|
||||||
|
rails users:show[username]
|
||||||
|
|
||||||
|
# Create a new user
|
||||||
|
rails users:create[username,email,password,company_id]
|
||||||
|
|
||||||
|
# Change user password
|
||||||
|
rails users:change_password[username,new_password]
|
||||||
|
|
||||||
|
# Delete a user
|
||||||
|
rails users:delete[username]
|
||||||
|
|
||||||
|
# Clean up test users (users with 'test' in username/email)
|
||||||
|
rails users:cleanup_test_users
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a new user (company_id is optional, uses first company if blank)
|
||||||
|
rails "users:create[john,john@example.com,securepass123]"
|
||||||
|
|
||||||
|
# Change password
|
||||||
|
rails "users:change_password[john,newpassword456]"
|
||||||
|
|
||||||
|
# Show user information
|
||||||
|
rails users:show[john]
|
||||||
|
|
||||||
|
# Delete user (with confirmation)
|
||||||
|
rails users:delete[john]
|
||||||
|
|
||||||
|
# List all users in a formatted table
|
||||||
|
rails users:list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automated Testing
|
||||||
|
|
||||||
|
**Run complete CRUD test suite:**
|
||||||
|
```bash
|
||||||
|
# Single command to test all user operations automatically
|
||||||
|
rails users:test_crud
|
||||||
|
```
|
||||||
|
|
||||||
|
This automated test will:
|
||||||
|
1. **Create** test users (dodavanje)
|
||||||
|
2. **List** created users
|
||||||
|
3. **Change** password (mijenjanje sifre)
|
||||||
|
4. **Show** user details
|
||||||
|
5. **Delete** user (brisanje)
|
||||||
|
6. **Clean up** all test data
|
||||||
|
|
||||||
|
### Manual Testing Workflow
|
||||||
|
|
||||||
|
1. **Create test users:**
|
||||||
|
```bash
|
||||||
|
rails "users:create[testuser1,test1@example.com,testpass123]"
|
||||||
|
rails "users:create[testuser2,test2@example.com,testpass456]"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Test your application** with the created users
|
||||||
|
|
||||||
|
3. **Clean up test data:**
|
||||||
|
```bash
|
||||||
|
# This will find and delete all users with 'test' in username or email
|
||||||
|
rails users:cleanup_test_users
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- All user passwords are encrypted using bcrypt
|
||||||
|
- Users must belong to a company
|
||||||
|
- Username and email must be unique
|
||||||
|
- Minimum password length is 6 characters
|
||||||
|
- The cleanup task only removes users with 'test' in their username or email for safety
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
before_action :set_locale
|
before_action :set_locale
|
||||||
|
before_action :require_login
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
@@ -8,27 +9,37 @@ class ApplicationController < ActionController::Base
|
|||||||
session[:locale] = I18n.locale
|
session[:locale] = I18n.locale
|
||||||
end
|
end
|
||||||
|
|
||||||
# Optional: Make locale persist across requests via URL helpers
|
|
||||||
def default_url_options
|
def default_url_options
|
||||||
{ locale: I18n.locale }
|
{ locale: I18n.locale }
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_company
|
def current_user
|
||||||
# This should be handled by your authentication system
|
@current_user ||= User.find(session[:user_id]) if session[:user_id]
|
||||||
# But for now, we'll use a placeholder
|
|
||||||
company_id = session[:company_id]
|
|
||||||
|
|
||||||
unless company_id && Company.exists?(company_id)
|
|
||||||
# If no company in session or it doesn't exist, use the first company
|
|
||||||
company_id = Company.first&.id
|
|
||||||
session[:company_id] = company_id
|
|
||||||
end
|
|
||||||
|
|
||||||
@company = Company.find(company_id) if company_id
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def logged_in?
|
||||||
|
!!current_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def require_login
|
||||||
|
return if logged_in?
|
||||||
|
|
||||||
|
flash[:alert] = t('sessions.login_required')
|
||||||
|
redirect_to login_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_company
|
||||||
|
return unless logged_in?
|
||||||
|
|
||||||
|
@company = current_user.company
|
||||||
|
|
||||||
|
return if @company
|
||||||
|
|
||||||
|
redirect_to companies_path, alert: 'No company found. Please create a company first.'
|
||||||
|
end
|
||||||
|
|
||||||
def current_company
|
def current_company
|
||||||
@company
|
@company
|
||||||
end
|
end
|
||||||
helper_method :current_company
|
helper_method :current_user, :logged_in?, :current_company
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -219,5 +219,10 @@ class ReservationsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
@company = Company.includes(:teams).find(company_id) if company_id
|
@company = Company.includes(:teams).find(company_id) if company_id
|
||||||
|
|
||||||
|
unless @company
|
||||||
|
redirect_to companies_path, alert: 'No company found. Please create a company first.'
|
||||||
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
25
app/controllers/sessions_controller.rb
Normal file
25
app/controllers/sessions_controller.rb
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
class SessionsController < ApplicationController
|
||||||
|
skip_before_action :require_login, only: %i[new create]
|
||||||
|
|
||||||
|
def new
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
user = User.find_by_login(params[:login])
|
||||||
|
|
||||||
|
if user&.authenticate(params[:password])
|
||||||
|
session[:user_id] = user.id
|
||||||
|
session[:company_id] = user.company_id
|
||||||
|
redirect_to root_path, notice: t('sessions.login_successful')
|
||||||
|
else
|
||||||
|
flash.now[:alert] = t('sessions.invalid_credentials')
|
||||||
|
render :new, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
session[:user_id] = nil
|
||||||
|
session[:company_id] = nil
|
||||||
|
redirect_to login_path, notice: t('sessions.logout_successful')
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,4 +2,5 @@ class Company < ApplicationRecord
|
|||||||
has_many :customers, dependent: :destroy
|
has_many :customers, dependent: :destroy
|
||||||
has_many :reservations, dependent: :destroy
|
has_many :reservations, dependent: :destroy
|
||||||
has_many :teams, dependent: :destroy
|
has_many :teams, dependent: :destroy
|
||||||
|
has_many :users, dependent: :destroy
|
||||||
end
|
end
|
||||||
|
|||||||
23
app/models/user.rb
Normal file
23
app/models/user.rb
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
class User < ApplicationRecord
|
||||||
|
has_secure_password
|
||||||
|
|
||||||
|
belongs_to :company
|
||||||
|
|
||||||
|
validates :username, presence: true, uniqueness: { case_sensitive: false }
|
||||||
|
validates :email, presence: true, uniqueness: { case_sensitive: false }, format: { with: URI::MailTo::EMAIL_REGEXP }
|
||||||
|
validates :password, length: { minimum: 6 }, if: -> { new_record? || password.present? }
|
||||||
|
validates :company_id, presence: true
|
||||||
|
|
||||||
|
before_save :downcase_username_and_email
|
||||||
|
|
||||||
|
def self.find_by_login(login)
|
||||||
|
find_by(username: login.downcase) || find_by(email: login.downcase)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def downcase_username_and_email
|
||||||
|
self.username = username.downcase.strip if username.present?
|
||||||
|
self.email = email.downcase.strip if email.present?
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -18,6 +18,15 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<% if logged_in? %>
|
||||||
|
<div class="fixed top-4 right-4 text-sm text-gray-600 bg-white px-3 py-1 rounded-md shadow-sm border">
|
||||||
|
<span class="lowercase"><%= current_user.username %></span>
|
||||||
|
<span class="mx-2">|</span>
|
||||||
|
<%= link_to t('sessions.logout_button'), logout_path, method: :delete,
|
||||||
|
class: "text-blue-600 hover:text-blue-800" %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<main class="container mx-auto mt-28 px-5 flex">
|
<main class="container mx-auto mt-28 px-5 flex">
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
<%= form.label :team_id, t('reservations.form.team') %>
|
<%= form.label :team_id, t('reservations.form.team') %>
|
||||||
<%= form.collection_select :team_id,
|
<%= form.collection_select :team_id,
|
||||||
@company.teams,
|
@company&.teams || [],
|
||||||
:id,
|
:id,
|
||||||
:name,
|
:name,
|
||||||
{ prompt: t('reservations.form.select_team') },
|
{ prompt: t('reservations.form.select_team') },
|
||||||
|
|||||||
@@ -28,10 +28,12 @@
|
|||||||
<label for="team-filter" class="mr-2 font-medium"><%= t('.filter_by_team') %>:</label>
|
<label for="team-filter" class="mr-2 font-medium"><%= t('.filter_by_team') %>:</label>
|
||||||
<select id="team-filter" data-main-calendar-target="teamFilter" data-action="change->main-calendar#filterByTeam" class="rounded-md border-gray-300 shadow-sm px-3 py-1 bg-white">
|
<select id="team-filter" data-main-calendar-target="teamFilter" data-action="change->main-calendar#filterByTeam" class="rounded-md border-gray-300 shadow-sm px-3 py-1 bg-white">
|
||||||
<option value="all"><%= t('.all_teams') %></option>
|
<option value="all"><%= t('.all_teams') %></option>
|
||||||
<% @company.teams.each do |team| %>
|
<% if @company&.teams %>
|
||||||
<option value="<%= team.id %>" style="background-color: <%= team_color(team.id) %>; color: #000000; padding-left: 10px;">
|
<% @company.teams.each do |team| %>
|
||||||
<%= team.name %>
|
<option value="<%= team.id %>" style="background-color: <%= team_color(team.id) %>; color: #000000; padding-left: 10px;">
|
||||||
</option>
|
<%= team.name %>
|
||||||
|
</option>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
24
app/views/sessions/new.html.erb
Normal file
24
app/views/sessions/new.html.erb
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<div class="max-w-md mx-auto mt-8 bg-white p-6 rounded-lg shadow-md">
|
||||||
|
<h2 class="text-2xl font-bold text-center mb-6"><%= t('sessions.login') %></h2>
|
||||||
|
|
||||||
|
<%= form_with url: login_path, method: :post, local: true, class: "space-y-4" do |form| %>
|
||||||
|
<div>
|
||||||
|
<%= form.label :login, t('sessions.username_or_email'), class: "block text-sm font-medium text-gray-700 mb-1" %>
|
||||||
|
<%= form.text_field :login, required: true,
|
||||||
|
class: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
|
||||||
|
placeholder: t('sessions.username_or_email_placeholder') %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<%= form.label :password, t('sessions.password'), class: "block text-sm font-medium text-gray-700 mb-1" %>
|
||||||
|
<%= form.password_field :password, required: true,
|
||||||
|
class: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
|
||||||
|
placeholder: t('sessions.password_placeholder') %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<%= form.submit t('sessions.login_button'),
|
||||||
|
class: "w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition duration-200" %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
@@ -131,4 +131,17 @@ bs:
|
|||||||
city: "Grad:"
|
city: "Grad:"
|
||||||
entity: "Entitet:"
|
entity: "Entitet:"
|
||||||
country: "Država:"
|
country: "Država:"
|
||||||
|
|
||||||
|
sessions:
|
||||||
|
login: "Prijava"
|
||||||
|
username_or_email: "Korisničko ime ili email"
|
||||||
|
username_or_email_placeholder: "Unesite korisničko ime ili email"
|
||||||
|
password: "Lozinka"
|
||||||
|
password_placeholder: "Unesite lozinku"
|
||||||
|
login_button: "Prijaviť se"
|
||||||
|
logout_button: "Odjavi se"
|
||||||
|
login_successful: "Uspješno ste se prijavili!"
|
||||||
|
logout_successful: "Uspješno ste se odjavili!"
|
||||||
|
invalid_credentials: "Neispravno korisničko ime/email ili lozinka"
|
||||||
|
login_required: "Molimo prijavite se da biste pristupili ovoj stranici"
|
||||||
|
|
||||||
@@ -167,3 +167,16 @@ en:
|
|||||||
city: "City:"
|
city: "City:"
|
||||||
entity: "Entity:"
|
entity: "Entity:"
|
||||||
country: "Country:"
|
country: "Country:"
|
||||||
|
|
||||||
|
sessions:
|
||||||
|
login: "Login"
|
||||||
|
username_or_email: "Username or Email"
|
||||||
|
username_or_email_placeholder: "Enter username or email"
|
||||||
|
password: "Password"
|
||||||
|
password_placeholder: "Enter password"
|
||||||
|
login_button: "Log In"
|
||||||
|
logout_button: "Log Out"
|
||||||
|
login_successful: "Successfully logged in!"
|
||||||
|
logout_successful: "Successfully logged out!"
|
||||||
|
invalid_credentials: "Invalid username/email or password"
|
||||||
|
login_required: "Please log in to access this page"
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
|
get '/login', to: 'sessions#new'
|
||||||
|
post '/login', to: 'sessions#create'
|
||||||
|
delete '/logout', to: 'sessions#destroy'
|
||||||
|
|
||||||
root "reservations#index"
|
root "reservations#index"
|
||||||
resources :customers, param: :composite_key do
|
resources :customers, param: :composite_key do
|
||||||
get :search, on: :collection
|
get :search, on: :collection
|
||||||
@@ -12,7 +16,7 @@ Rails.application.routes.draw do
|
|||||||
# Can be used by load balancers and uptime monitors to verify that the app is live.
|
# Can be used by load balancers and uptime monitors to verify that the app is live.
|
||||||
get "up" => "rails/health#show", as: :rails_health_check
|
get "up" => "rails/health#show", as: :rails_health_check
|
||||||
|
|
||||||
# config/routes.rb
|
# config/routes.rb
|
||||||
get "/service-worker.js" => "service_worker#service_worker"
|
get "/service-worker.js" => "service_worker#service_worker"
|
||||||
get "/manifest.json" => "service_worker#manifest"
|
get "/manifest.json" => "service_worker#manifest"
|
||||||
# Defines the root path route ("/")
|
# Defines the root path route ("/")
|
||||||
|
|||||||
14
db/migrate/20250731113948_create_users.rb
Normal file
14
db/migrate/20250731113948_create_users.rb
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
class CreateUsers < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
create_table :users do |t|
|
||||||
|
t.string :username
|
||||||
|
t.string :email
|
||||||
|
t.string :password_digest
|
||||||
|
t.references :company, null: false, foreign_key: true
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
add_index :users, :username, unique: true
|
||||||
|
add_index :users, :email, unique: true
|
||||||
|
end
|
||||||
|
end
|
||||||
6
db/migrate/20250731114403_add_email_to_users.rb
Normal file
6
db/migrate/20250731114403_add_email_to_users.rb
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
class AddEmailToUsers < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
add_column :users, :email, :string
|
||||||
|
add_index :users, :email, unique: true
|
||||||
|
end
|
||||||
|
end
|
||||||
15
db/schema.rb
generated
15
db/schema.rb
generated
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.1].define(version: 2025_07_04_092438) do
|
ActiveRecord::Schema[7.1].define(version: 2025_07_31_114403) do
|
||||||
create_table "companies", force: :cascade do |t|
|
create_table "companies", force: :cascade do |t|
|
||||||
t.string "name"
|
t.string "name"
|
||||||
t.string "id_number"
|
t.string "id_number"
|
||||||
@@ -66,8 +66,21 @@ ActiveRecord::Schema[7.1].define(version: 2025_07_04_092438) do
|
|||||||
t.index ["company_id"], name: "index_teams_on_company_id"
|
t.index ["company_id"], name: "index_teams_on_company_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "users", force: :cascade do |t|
|
||||||
|
t.string "username"
|
||||||
|
t.string "password_digest"
|
||||||
|
t.integer "company_id", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.string "email"
|
||||||
|
t.index ["company_id"], name: "index_users_on_company_id"
|
||||||
|
t.index ["email"], name: "index_users_on_email", unique: true
|
||||||
|
t.index ["username"], name: "index_users_on_username", unique: true
|
||||||
|
end
|
||||||
|
|
||||||
add_foreign_key "reservations", "companies"
|
add_foreign_key "reservations", "companies"
|
||||||
add_foreign_key "reservations", "customers", column: ["customer_first_name", "customer_surname", "customer_original_phone"], primary_key: ["first_name", "surname", "original_phone"]
|
add_foreign_key "reservations", "customers", column: ["customer_first_name", "customer_surname", "customer_original_phone"], primary_key: ["first_name", "surname", "original_phone"]
|
||||||
add_foreign_key "reservations", "teams"
|
add_foreign_key "reservations", "teams"
|
||||||
add_foreign_key "teams", "companies"
|
add_foreign_key "teams", "companies"
|
||||||
|
add_foreign_key "users", "companies"
|
||||||
end
|
end
|
||||||
|
|||||||
31
db/seeds.rb
31
db/seeds.rb
@@ -7,3 +7,34 @@
|
|||||||
# ["Action", "Comedy", "Drama", "Horror"].each do |genre_name|
|
# ["Action", "Comedy", "Drama", "Horror"].each do |genre_name|
|
||||||
# MovieGenre.find_or_create_by!(name: genre_name)
|
# MovieGenre.find_or_create_by!(name: genre_name)
|
||||||
# end
|
# end
|
||||||
|
# Create a default company if none exists
|
||||||
|
default_company = Company.find_or_create_by!(name: 'Default Company') do |company|
|
||||||
|
company.id_number = 'COMP001'
|
||||||
|
company.vat_number = 'VAT001'
|
||||||
|
company.address_line_one = '123 Main Street'
|
||||||
|
company.city = 'Default City'
|
||||||
|
company.country = 'Default Country'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create default teams for the company
|
||||||
|
teams_data = [
|
||||||
|
{ name: 'Team Alpha' },
|
||||||
|
{ name: 'Team Beta' },
|
||||||
|
{ name: 'Team Gamma' }
|
||||||
|
]
|
||||||
|
|
||||||
|
teams_data.each do |team_attrs|
|
||||||
|
Team.find_or_create_by!(name: team_attrs[:name], company: default_company)
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "Seeded default company: #{default_company.name}"
|
||||||
|
puts "Seeded #{default_company.teams.count} teams"
|
||||||
|
|
||||||
|
# Create default user for the company
|
||||||
|
default_user = User.find_or_create_by!(username: 'admin', company: default_company) do |user|
|
||||||
|
user.email = 'admin@company.ba'
|
||||||
|
user.password = 'password123'
|
||||||
|
user.password_confirmation = 'password123'
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "Seeded default user: #{default_user.username} (#{default_user.email})"
|
||||||
|
|||||||
271
lib/tasks/users.rake
Normal file
271
lib/tasks/users.rake
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
namespace :users do
|
||||||
|
desc "Create a new user"
|
||||||
|
task :create, %i[username email password company_id] => :environment do |_t, args|
|
||||||
|
username = args[:username] || ask("Username: ")
|
||||||
|
email = args[:email] || ask("Email: ")
|
||||||
|
password = args[:password] || ask("Password: ") { |q| q.echo = "*" }
|
||||||
|
company_id = find_or_validate_company(args[:company_id])
|
||||||
|
|
||||||
|
user = User.new(
|
||||||
|
username: username,
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
password_confirmation: password,
|
||||||
|
company_id: company_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if user.save
|
||||||
|
puts "User created successfully!"
|
||||||
|
puts " Username: #{user.username}"
|
||||||
|
puts " Email: #{user.email}"
|
||||||
|
puts " Company: #{user.company.name}"
|
||||||
|
else
|
||||||
|
puts "Failed to create user:"
|
||||||
|
user.errors.full_messages.each { |msg| puts " - #{msg}" }
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Change user password"
|
||||||
|
task :change_password, %i[username new_password] => :environment do |_t, args|
|
||||||
|
username = args[:username] || ask("Username: ")
|
||||||
|
new_password = args[:new_password] || ask("New password: ") { |q| q.echo = "*" }
|
||||||
|
|
||||||
|
user = User.find_by(username: username)
|
||||||
|
unless user
|
||||||
|
puts "User '#{username}' not found."
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
user.password = new_password
|
||||||
|
user.password_confirmation = new_password
|
||||||
|
|
||||||
|
if user.save
|
||||||
|
puts "Password changed successfully for user '#{username}'"
|
||||||
|
else
|
||||||
|
puts "Failed to change password:"
|
||||||
|
user.errors.full_messages.each { |msg| puts " - #{msg}" }
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Delete a user"
|
||||||
|
task :delete, %i[username] => :environment do |_t, args|
|
||||||
|
username = args[:username] || ask("Username to delete: ")
|
||||||
|
|
||||||
|
user = User.find_by(username: username)
|
||||||
|
unless user
|
||||||
|
puts "User '#{username}' not found."
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "User details:"
|
||||||
|
puts " Username: #{user.username}"
|
||||||
|
puts " Email: #{user.email}"
|
||||||
|
puts " Company: #{user.company.name}"
|
||||||
|
|
||||||
|
confirm = ask("Are you sure you want to delete this user? (yes/no): ")
|
||||||
|
unless confirm.downcase == 'yes'
|
||||||
|
puts "User deletion cancelled."
|
||||||
|
exit 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if user.destroy
|
||||||
|
puts "User '#{username}' deleted successfully."
|
||||||
|
else
|
||||||
|
puts "Failed to delete user:"
|
||||||
|
user.errors.full_messages.each { |msg| puts " - #{msg}" }
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "List all users"
|
||||||
|
task list: :environment do
|
||||||
|
users = User.includes(:company).order(:username)
|
||||||
|
|
||||||
|
if users.empty?
|
||||||
|
puts "No users found."
|
||||||
|
exit 0
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "Users List:"
|
||||||
|
puts "=" * 80
|
||||||
|
printf "%-20s %-30s %-20s %s\n", "USERNAME", "EMAIL", "COMPANY", "CREATED"
|
||||||
|
puts "-" * 80
|
||||||
|
|
||||||
|
users.each do |user|
|
||||||
|
printf "%-20s %-30s %-20s %s\n",
|
||||||
|
user.username,
|
||||||
|
user.email,
|
||||||
|
user.company.name,
|
||||||
|
user.created_at.strftime("%Y-%m-%d")
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "-" * 80
|
||||||
|
puts "Total: #{users.count} users"
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Run automated CRUD tests for user management"
|
||||||
|
task test_crud: :environment do
|
||||||
|
puts "=" * 60
|
||||||
|
puts "AUTOMATED USER CRUD TEST"
|
||||||
|
puts "=" * 60
|
||||||
|
|
||||||
|
# Test 1: Create users
|
||||||
|
puts "\n1. TESTING USER CREATION (dodavanje):"
|
||||||
|
puts "-" * 40
|
||||||
|
|
||||||
|
test_users = [
|
||||||
|
{ username: 'testuser1', email: 'test1@example.com', password: 'testpass123' },
|
||||||
|
{ username: 'testuser2', email: 'test2@example.com', password: 'testpass456' }
|
||||||
|
]
|
||||||
|
|
||||||
|
test_users.each do |user_data|
|
||||||
|
company_id = find_or_validate_company(nil)
|
||||||
|
user = User.new(
|
||||||
|
username: user_data[:username],
|
||||||
|
email: user_data[:email],
|
||||||
|
password: user_data[:password],
|
||||||
|
password_confirmation: user_data[:password],
|
||||||
|
company_id: company_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if user.save
|
||||||
|
puts "✓ Created user: #{user.username} (#{user.email})"
|
||||||
|
else
|
||||||
|
puts "✗ Failed to create #{user_data[:username]}: #{user.errors.full_messages.join(', ')}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test 2: List users
|
||||||
|
puts "\n2. TESTING USER LISTING:"
|
||||||
|
puts "-" * 40
|
||||||
|
users = User.where("username LIKE ?", "%test%").order(:username)
|
||||||
|
users.each do |user|
|
||||||
|
puts "✓ Found user: #{user.username} (#{user.email}) - Company: #{user.company.name}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test 3: Change password (mijenjanje sifre)
|
||||||
|
puts "\n3. TESTING PASSWORD CHANGE (mijenjanje sifre):"
|
||||||
|
puts "-" * 40
|
||||||
|
test_user = User.find_by(username: 'testuser1')
|
||||||
|
if test_user
|
||||||
|
test_user.password = 'newpassword789'
|
||||||
|
test_user.password_confirmation = 'newpassword789'
|
||||||
|
if test_user.save
|
||||||
|
puts "✓ Password changed for: #{test_user.username}"
|
||||||
|
else
|
||||||
|
puts "✗ Failed to change password: #{test_user.errors.full_messages.join(', ')}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test 4: Show user details
|
||||||
|
puts "\n4. TESTING USER DETAILS:"
|
||||||
|
puts "-" * 40
|
||||||
|
if test_user
|
||||||
|
puts "✓ User: #{test_user.username}"
|
||||||
|
puts " Email: #{test_user.email}"
|
||||||
|
puts " Company: #{test_user.company.name}"
|
||||||
|
puts " Created: #{test_user.created_at.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test 5: Delete user (brisanje)
|
||||||
|
puts "\n5. TESTING USER DELETION (brisanje):"
|
||||||
|
puts "-" * 40
|
||||||
|
delete_user = User.find_by(username: 'testuser2')
|
||||||
|
if delete_user
|
||||||
|
username = delete_user.username
|
||||||
|
if delete_user.destroy
|
||||||
|
puts "✓ Deleted user: #{username}"
|
||||||
|
else
|
||||||
|
puts "✗ Failed to delete user: #{delete_user.errors.full_messages.join(', ')}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test 6: Final cleanup
|
||||||
|
puts "\n6. CLEANUP:"
|
||||||
|
puts "-" * 40
|
||||||
|
remaining_test_users = User.where("username LIKE ? OR email LIKE ?", "%test%", "%test%")
|
||||||
|
deleted_count = remaining_test_users.count
|
||||||
|
remaining_test_users.destroy_all
|
||||||
|
puts "✓ Cleaned up #{deleted_count} remaining test users"
|
||||||
|
|
||||||
|
puts "\n" + ("=" * 60)
|
||||||
|
puts "CRUD TEST COMPLETED SUCCESSFULLY!"
|
||||||
|
puts "All operations (dodavanje, mijenjanje sifre, brisanje) tested."
|
||||||
|
puts "=" * 60
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Clean up test users (users with 'test' in username or email)"
|
||||||
|
task cleanup_test_users: :environment do
|
||||||
|
test_users = User.where("username LIKE ? OR email LIKE ?", "%test%", "%test%")
|
||||||
|
|
||||||
|
if test_users.empty?
|
||||||
|
puts "No test users found to clean up."
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "Found #{test_users.count} test users to delete:"
|
||||||
|
test_users.each do |user|
|
||||||
|
puts " - #{user.username} (#{user.email})"
|
||||||
|
end
|
||||||
|
|
||||||
|
confirm = ask("Delete all test users? (yes/no): ")
|
||||||
|
if confirm.downcase == 'yes'
|
||||||
|
deleted_count = test_users.count
|
||||||
|
test_users.destroy_all
|
||||||
|
puts "Deleted #{deleted_count} test users successfully."
|
||||||
|
else
|
||||||
|
puts "Cleanup cancelled."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Show user details"
|
||||||
|
task :show, %i[username] => :environment do |_t, args|
|
||||||
|
username = args[:username] || ask("Username: ")
|
||||||
|
|
||||||
|
user = User.find_by(username: username)
|
||||||
|
unless user
|
||||||
|
puts "User '#{username}' not found."
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "User Details:"
|
||||||
|
puts " Username: #{user.username}"
|
||||||
|
puts " Email: #{user.email}"
|
||||||
|
puts " Company: #{user.company.name} (ID: #{user.company.id})"
|
||||||
|
puts " Created: #{user.created_at.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
puts " Updated: #{user.updated_at.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def find_or_validate_company(company_id)
|
||||||
|
if company_id.blank?
|
||||||
|
company = Company.first
|
||||||
|
unless company
|
||||||
|
puts "No companies found. Please create a company first."
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
puts "Using company: #{company.name} (ID: #{company.id})"
|
||||||
|
company.id
|
||||||
|
else
|
||||||
|
company = Company.find_by(id: company_id)
|
||||||
|
unless company
|
||||||
|
puts "Company with ID #{company_id} not found."
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
company_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def ask(prompt, &_block)
|
||||||
|
require 'io/console'
|
||||||
|
print prompt
|
||||||
|
if block_given?
|
||||||
|
yield.call
|
||||||
|
else
|
||||||
|
$stdin.gets.chomp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user