Move github source to private server
This commit is contained in:
14
.dockerignore
Normal file
14
.dockerignore
Normal file
@@ -0,0 +1,14 @@
|
||||
.git
|
||||
.gitignore
|
||||
log/*
|
||||
tmp/*
|
||||
db/*.sqlite3
|
||||
db/*.sqlite3-journal
|
||||
public/assets
|
||||
node_modules
|
||||
.bundle
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
README.md
|
||||
test/*
|
||||
spec/*
|
||||
41
Dockerfile
Normal file
41
Dockerfile
Normal file
@@ -0,0 +1,41 @@
|
||||
# Use Ruby 2.6.5 as the base image
|
||||
FROM ruby:2.6.5
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update -qq && apt-get install -y \
|
||||
build-essential \
|
||||
libpq-dev \
|
||||
nodejs \
|
||||
libsqlite3-dev \
|
||||
yarn
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install bundler
|
||||
RUN gem install bundler -v 2.1.4
|
||||
|
||||
# Copy Gemfile and Gemfile.lock
|
||||
COPY Gemfile Gemfile.lock ./
|
||||
|
||||
# Install dependencies
|
||||
RUN bundle install
|
||||
|
||||
# Copy the rest of the application code
|
||||
COPY . .
|
||||
|
||||
# Set environment variables
|
||||
ENV RAILS_ENV=production
|
||||
ENV RAILS_SERVE_STATIC_FILES=true
|
||||
ENV RAILS_LOG_TO_STDOUT=true
|
||||
|
||||
# Precompile assets
|
||||
# Note: SECRET_KEY_BASE is required for asset precompilation in some Rails versions
|
||||
# You can provide a dummy value here if it's not strictly checked during precompile
|
||||
RUN bundle exec rake assets:precompile SECRET_KEY_BASE=dummy_key
|
||||
|
||||
# Expose port 8080 (Cloud Run default)
|
||||
EXPOSE 8080
|
||||
|
||||
# Start the application
|
||||
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
|
||||
4
Gemfile
4
Gemfile
@@ -8,8 +8,8 @@ end
|
||||
|
||||
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
||||
gem 'rails', '~> 5.1.0'
|
||||
# Use sqlite3 as the database for Active Record
|
||||
gem 'sqlite3'
|
||||
# Use PostgreSQL as the database for Active Record
|
||||
gem 'pg'
|
||||
# Use Puma as the app server
|
||||
gem 'puma', '~> 3.7'
|
||||
# Use SCSS for stylesheets
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
include AuthorizationConcern
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :configure_permitted_parameters, if: :devise_controller?
|
||||
before_action :set_current_user_department
|
||||
|
||||
|
||||
31
app/controllers/comments_controller.rb
Normal file
31
app/controllers/comments_controller.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
class CommentsController < ApplicationController
|
||||
before_action :set_task
|
||||
|
||||
def create
|
||||
authorize_task!
|
||||
@comment = @task.comments.build(comment_params)
|
||||
@comment.user = current_user
|
||||
|
||||
if @comment.save
|
||||
respond_to do |format|
|
||||
format.html { redirect_to @task, notice: 'Comment added.' }
|
||||
format.js
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html { redirect_to @task, alert: 'Comment cannot be blank.' }
|
||||
format.js
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_task
|
||||
@task = Task.find(params[:task_id])
|
||||
end
|
||||
|
||||
def comment_params
|
||||
params.require(:comment).permit(:content)
|
||||
end
|
||||
end
|
||||
@@ -53,7 +53,6 @@ class TasksController < ApplicationController
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize_task_update!(@task)
|
||||
@departments = accessible_departments.ordered
|
||||
end
|
||||
|
||||
@@ -72,7 +71,14 @@ class TasksController < ApplicationController
|
||||
end
|
||||
|
||||
def update
|
||||
if @task.update(task_params)
|
||||
updated_params = task_params.to_h
|
||||
if updated_params[:status] == "1"
|
||||
updated_params[:status] = "completed"
|
||||
elsif updated_params[:status] == "0"
|
||||
updated_params[:status] = "open"
|
||||
end
|
||||
|
||||
if @task.update(updated_params)
|
||||
respond_to do |format|
|
||||
format.html { redirect_to @task, notice: 'Task was successfully updated.' }
|
||||
format.js
|
||||
|
||||
6
app/models/comment.rb
Normal file
6
app/models/comment.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
class Comment < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :task
|
||||
|
||||
validates :content, presence: true
|
||||
end
|
||||
@@ -4,6 +4,7 @@ class Task < ApplicationRecord
|
||||
belongs_to :creator, class_name: 'User'
|
||||
|
||||
has_many :task_activities, dependent: :destroy
|
||||
has_many :comments, dependent: :destroy
|
||||
|
||||
enum priority: {
|
||||
planning: 'planning',
|
||||
|
||||
@@ -7,6 +7,7 @@ class User < ApplicationRecord
|
||||
belongs_to :department, optional: true
|
||||
has_many :assigned_tasks, class_name: 'Task', foreign_key: 'assignee_id'
|
||||
has_many :created_tasks, class_name: 'Task', foreign_key: 'creator_id'
|
||||
has_many :comments, dependent: :destroy
|
||||
|
||||
enum role: { admin: 'admin', manager: 'manager', employee: 'employee' }
|
||||
|
||||
|
||||
9
app/views/comments/_comment.html.erb
Normal file
9
app/views/comments/_comment.html.erb
Normal file
@@ -0,0 +1,9 @@
|
||||
<div class="comment" id="comment_<%= comment.id %>">
|
||||
<div class="comment-header">
|
||||
<span class="comment-user"><%= comment.user.name %></span>
|
||||
<span class="comment-date"><%= time_ago_in_words(comment.created_at) %> ago</span>
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<%= simple_format(comment.content) %>
|
||||
</div>
|
||||
</div>
|
||||
8
app/views/comments/_form.html.erb
Normal file
8
app/views/comments/_form.html.erb
Normal file
@@ -0,0 +1,8 @@
|
||||
<%= form_with(model: [task, comment], local: false, class: "comment-form") do |f| %>
|
||||
<div class="field">
|
||||
<%= f.text_area :content, placeholder: "Add a comment...", class: "comment-textarea", required: true %>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<%= f.submit "Post Comment", class: "submit-btn" %>
|
||||
</div>
|
||||
<% end %>
|
||||
16
app/views/comments/create.js.erb
Normal file
16
app/views/comments/create.js.erb
Normal file
@@ -0,0 +1,16 @@
|
||||
var commentsList = document.getElementById('comments-list');
|
||||
var commentForm = document.querySelector('.comment-form');
|
||||
|
||||
if (commentsList && <%= @comment.persisted? %>) {
|
||||
var newComment = '<%= j render partial: "comments/comment", locals: { comment: @comment } %>';
|
||||
commentsList.insertAdjacentHTML('afterbegin', newComment);
|
||||
commentForm.reset();
|
||||
|
||||
// Optional: add a small animation
|
||||
var commentElement = document.getElementById('comment_<%= @comment.id %>');
|
||||
commentElement.style.opacity = '0';
|
||||
commentElement.style.transition = 'opacity 0.5s';
|
||||
setTimeout(function() {
|
||||
commentElement.style.opacity = '1';
|
||||
}, 10);
|
||||
}
|
||||
@@ -1,36 +1,50 @@
|
||||
<h1>Departments</h1>
|
||||
<div class="dashboard-header">
|
||||
<h1>Departments</h1>
|
||||
<% if current_user&.admin? %>
|
||||
<%= link_to 'New Department', new_department_path, class: 'btn btn-success' %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if flash[:notice] %>
|
||||
<div class="notice"><%= flash[:notice] %></div>
|
||||
<% end %>
|
||||
|
||||
<div class="departments-grid">
|
||||
<% if @departments.empty? %>
|
||||
<p class="empty-state">No departments found.</p>
|
||||
<% else %>
|
||||
<% @departments.each do |department| %>
|
||||
<div class="department-card">
|
||||
<h3><%= link_to department.name, department_path(department) %></h3>
|
||||
<p><%= department.description %></p>
|
||||
|
||||
<div class="department-stats">
|
||||
<div class="stat">
|
||||
<strong>Users:</strong> <%= department.user_count %>
|
||||
<div class="content-container">
|
||||
<div class="departments-grid">
|
||||
<% if @departments.empty? %>
|
||||
<div class="empty-state-card">
|
||||
<p class="empty-state">No departments found.</p>
|
||||
</div>
|
||||
<% else %>
|
||||
<% @departments.each do |department| %>
|
||||
<div class="department-card-modern">
|
||||
<div class="department-card-header">
|
||||
<h3><%= link_to department.name, department_path(department) %></h3>
|
||||
<div class="department-actions-mini">
|
||||
<% if current_user&.admin? %>
|
||||
<%= link_to 'Edit', edit_department_path(department), class: 'btn-mini' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<strong>Tasks:</strong> <%= department.task_count %>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<strong>Open:</strong> <%= department.incomplete_task_count %>
|
||||
|
||||
<p class="department-desc"><%= department.description&.truncate(100) || "No description provided." %></p>
|
||||
|
||||
<div class="department-stats-grid">
|
||||
<div class="dept-stat-box">
|
||||
<span class="dept-stat-label">Users</span>
|
||||
<span class="dept-stat-value"><%= department.user_count %></span>
|
||||
</div>
|
||||
<div class="dept-stat-box">
|
||||
<span class="dept-stat-label">Tasks</span>
|
||||
<span class="dept-stat-value"><%= department.task_count %></span>
|
||||
</div>
|
||||
<div class="dept-stat-box">
|
||||
<span class="dept-stat-label">Open</span>
|
||||
<span class="dept-stat-value text-primary"><%= department.incomplete_task_count %></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="actions-section">
|
||||
<% if current_user&.admin? %>
|
||||
<%= link_to 'New Department', new_department_path, class: 'action-btn primary' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Project Management System</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<%= csrf_meta_tags %>
|
||||
|
||||
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
|
||||
|
||||
@@ -1,58 +1,53 @@
|
||||
<div id="task_<%= task.id %>" class="task <%= 'completed' if task.status == 'completed' %>">
|
||||
<div class="task-header">
|
||||
<div class="task-priority" style="color: <%= task.priority_color %>">
|
||||
<%= task.priority_icon %> <%= task.priority.humanize %>
|
||||
</div>
|
||||
<div class="task-status-badge" style="background-color: <%= task.status_badge[:color] %>">
|
||||
<%= task.status_badge[:text] %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= form_with(model: task, local: false, method: :patch, class: "toggle-form") do |form| %>
|
||||
<div class="task-content">
|
||||
<%= form.check_box :status, { checked: task.status == 'completed', onchange: "this.form.submit();" }, { class: "task-checkbox" } %>
|
||||
|
||||
<div class="task-info">
|
||||
<span class="task-title"
|
||||
title="<%= task.description&.truncate(100) || 'No description' %>"
|
||||
data-description="<%= task.description || '' %>">
|
||||
<%= task.title %>
|
||||
</span>
|
||||
<% if task.description.present? %>
|
||||
<span class="description-indicator">📝</span>
|
||||
<div id="task_<%= task.id %>" class="task-card <%= task.status %>" data-priority="<%= task.priority %>">
|
||||
<div class="task-card-header">
|
||||
<div class="task-card-title-section">
|
||||
<div class="task-checkbox-wrapper">
|
||||
<%= form_with(model: task, local: false, method: :patch) do |f| %>
|
||||
<%= f.check_box :status, { checked: task.status == 'completed', onchange: "this.form.submit();" }, "completed", "open" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<h3 class="task-card-title"><%= task.title %></h3>
|
||||
<% if task.description.present? %>
|
||||
<span class="task-desc-indicator" title="Has description">📝</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="task-meta">
|
||||
<% if task.department %>
|
||||
<span class="department-tag">
|
||||
<%= task.department.name %>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<% if task.assignee %>
|
||||
<span class="assignee-tag">
|
||||
Assigned to: <%= task.assignee.name %>
|
||||
<div class="task-card-badges">
|
||||
<span class="badge-priority" style="color: <%= task.priority_color %>; border-color: <%= task.priority_color %>22; background-color: <%= task.priority_color %>11;">
|
||||
<%= task.priority_icon %> <%= task.priority.humanize %>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<span class="created-tag">
|
||||
Created: <%= task.created_at.strftime('%m/%d/%Y') %>
|
||||
</span>
|
||||
<span class="badge-status" style="background-color: <%= task.status_badge[:color] %>">
|
||||
<%= task.status_badge[:text] %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="task-actions">
|
||||
<%= link_to 'View', task, class: 'details-btn' %>
|
||||
<% if task.updateable_by?(current_user) %>
|
||||
<%= link_to 'Edit', edit_task_path(task), class: 'edit-btn' %>
|
||||
<% end %>
|
||||
<% if task.assign?(current_user) && !task.assignee %>
|
||||
<%= link_to 'Assign', assign_task_path(task), method: :patch, class: 'assign-btn' %>
|
||||
<% end %>
|
||||
<% if task.updateable_by?(current_user) || current_user.admin? %>
|
||||
<%= link_to 'Delete', task, method: :delete, data: { confirm: 'Are you sure?' }, class: 'delete-btn' %>
|
||||
<% end %>
|
||||
|
||||
<div class="task-card-body">
|
||||
<div class="task-card-info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">Dept</span>
|
||||
<span class="info-value"><%= task.department&.name || 'Personal' %></span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">Assignee</span>
|
||||
<span class="info-value"><%= task.assignee&.name || 'Unassigned' %></span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">Created</span>
|
||||
<span class="info-value"><%= task.created_at.strftime('%b %d, %Y') %></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="task-card-footer">
|
||||
<div class="task-card-actions">
|
||||
<%= link_to 'View', task, class: 'btn-task-action' %>
|
||||
<% if task.updateable_by?(current_user) %>
|
||||
<%= link_to 'Edit', edit_task_path(task), class: 'btn-task-action' %>
|
||||
<% end %>
|
||||
<% if task.updateable_by?(current_user) || current_user.admin? %>
|
||||
<%= link_to 'Delete', task, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn-task-action action-delete' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -119,10 +119,10 @@
|
||||
</div>
|
||||
|
||||
<div class="dashboard-actions">
|
||||
<%= link_to 'View All Tasks', tasks_path, class: 'action-btn primary' %>
|
||||
<%= link_to 'Create New Task', new_task_path, class: 'action-btn success' %>
|
||||
<%= link_to 'View All Tasks', tasks_path, class: 'btn btn-primary' %>
|
||||
<%= link_to 'Create New Task', new_task_path, class: 'btn btn-success' %>
|
||||
<% if current_user.admin? || current_user.manager? %>
|
||||
<%= link_to 'Manage Departments', departments_path, class: 'action-btn secondary' %>
|
||||
<%= link_to 'Manage Departments', departments_path, class: 'btn btn-secondary' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -21,52 +21,38 @@
|
||||
<div class="sidebar">
|
||||
<div class="filter-section">
|
||||
<h3>Filters</h3>
|
||||
|
||||
<% if accessible_departments.count > 1 %>
|
||||
<div class="filter-group">
|
||||
<label>Department</label>
|
||||
<%= form_with(url: tasks_path, method: :get, local: true, class: "filter-form") do |f| %>
|
||||
<%= form_with(url: tasks_path, method: :get, local: true, class: "combined-filter-form") do |f| %>
|
||||
<% if accessible_departments.count > 1 %>
|
||||
<div class="filter-group">
|
||||
<label>Department</label>
|
||||
<%= f.select :department_id,
|
||||
options_for_select([[ "All Departments", "" ]] + accessible_departments.map { |d| [d.name, d.id] },
|
||||
params[:department_id]),
|
||||
{ onchange: "this.form.submit();" },
|
||||
{ class: "filter-select" } %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>Priority</label>
|
||||
<%= form_with(url: tasks_path, method: :get, local: true, class: "filter-form") do |f| %>
|
||||
{},
|
||||
{ class: "filter-select", onchange: "this.form.submit();" } %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>Priority</label>
|
||||
<%= f.select :priority,
|
||||
options_for_select([[ "All Priorities", "" ]] + Task.priorities.keys.map { |p| [p.humanize, p] },
|
||||
params[:priority]),
|
||||
{ onchange: "this.form.submit();" },
|
||||
{ class: "filter-select" } %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>Status</label>
|
||||
<%= form_with(url: tasks_path, method: :get, local: true, class: "filter-form") do |f| %>
|
||||
{},
|
||||
{ class: "filter-select", onchange: "this.form.submit();" } %>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>Status</label>
|
||||
<%= f.select :status,
|
||||
options_for_select([[ "All Statuses", "" ]] + Task.statuses.keys.map { |s| [s.humanize, s] },
|
||||
params[:status]),
|
||||
{ onchange: "this.form.submit();" },
|
||||
{ class: "filter-select" } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions-section">
|
||||
<h3>Actions</h3>
|
||||
<%= link_to 'Dashboard', dashboard_path, class: 'action-btn' %>
|
||||
<%= link_to 'My Tasks', my_tasks_path, class: 'action-btn' if current_user.employee? %>
|
||||
<%= link_to 'Departments', departments_path, class: 'action-btn' if current_user.admin? || current_user.manager? %>
|
||||
<% if current_user.admin? %>
|
||||
<%= link_to 'New User', new_user_registration_path, class: 'action-btn' %>
|
||||
{},
|
||||
{ class: "filter-select", onchange: "this.form.submit();" } %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
|
||||
@@ -24,6 +24,15 @@
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="comments-section">
|
||||
<h2>Conversations</h2>
|
||||
<%= render 'comments/form', task: @task, comment: Comment.new %>
|
||||
|
||||
<div id="comments-list">
|
||||
<%= render partial: 'comments/comment', collection: @task.comments.order(created_at: :desc) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="task-actions">
|
||||
<%= form_with(model: @task, local: false, method: :patch, class: "toggle-status-form") do |form| %>
|
||||
<%= form.hidden_field :completed, value: !@task.completed %>
|
||||
|
||||
26
cloudbuild.yaml
Normal file
26
cloudbuild.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
steps:
|
||||
# Build the container image
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: ['build', '-t', 'gcr.io/$PROJECT_ID/todo-app', '.']
|
||||
|
||||
# Push the container image to Container Registry
|
||||
- name: 'gcr.io/cloud-builders/docker'
|
||||
args: ['push', 'gcr.io/$PROJECT_ID/todo-app']
|
||||
|
||||
# Deploy container image to Cloud Run
|
||||
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
|
||||
entrypoint: gcloud
|
||||
args:
|
||||
- 'run'
|
||||
- 'deploy'
|
||||
- 'todo-app'
|
||||
- '--image'
|
||||
- 'gcr.io/$PROJECT_ID/todo-app'
|
||||
- '--region'
|
||||
- 'us-central1'
|
||||
- '--platform'
|
||||
- 'managed'
|
||||
- '--allow-unauthenticated'
|
||||
|
||||
images:
|
||||
- 'gcr.io/$PROJECT_ID/todo-app'
|
||||
@@ -1,25 +1,22 @@
|
||||
# SQLite version 3.x
|
||||
# gem install sqlite3
|
||||
#
|
||||
# Ensure the SQLite 3 gem is defined in your Gemfile
|
||||
# gem 'sqlite3'
|
||||
#
|
||||
default: &default
|
||||
adapter: sqlite3
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
# For details on connection pooling, see Rails configuration guide
|
||||
# http://guides.rubyonrails.org/configuring.html#database-pooling
|
||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
timeout: 5000
|
||||
|
||||
development:
|
||||
<<: *default
|
||||
database: db/development.sqlite3
|
||||
database: todo_development
|
||||
|
||||
# Warning: The database defined as "test" will be erased and
|
||||
# re-generated from your development database when you run "rake".
|
||||
# Do not set this db to the same as development or production.
|
||||
test:
|
||||
<<: *default
|
||||
database: db/test.sqlite3
|
||||
database: todo_test
|
||||
|
||||
production:
|
||||
<<: *default
|
||||
database: db/production.sqlite3
|
||||
database: <%= ENV['DB_NAME'] || 'todo_production' %>
|
||||
username: <%= ENV['DB_USER'] %>
|
||||
password: <%= ENV['DB_PASSWORD'] %>
|
||||
host: <%= ENV['DB_HOST'] %>
|
||||
# When using Cloud SQL with Cloud Run, the host is often /cloudsql/CONNECTION_NAME
|
||||
|
||||
@@ -11,6 +11,7 @@ Rails.application.routes.draw do
|
||||
member do
|
||||
patch :assign
|
||||
end
|
||||
resources :comments, only: [:create]
|
||||
end
|
||||
|
||||
root 'tasks#index'
|
||||
|
||||
11
db/migrate/20260128050859_create_comments.rb
Normal file
11
db/migrate/20260128050859_create_comments.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class CreateComments < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
create_table :comments do |t|
|
||||
t.text :content
|
||||
t.references :user, foreign_key: true
|
||||
t.references :task, foreign_key: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
12
db/schema.rb
12
db/schema.rb
@@ -10,7 +10,17 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20260122043706) do
|
||||
ActiveRecord::Schema.define(version: 20260128050859) do
|
||||
|
||||
create_table "comments", force: :cascade do |t|
|
||||
t.text "content"
|
||||
t.integer "user_id"
|
||||
t.integer "task_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["task_id"], name: "index_comments_on_task_id"
|
||||
t.index ["user_id"], name: "index_comments_on_user_id"
|
||||
end
|
||||
|
||||
create_table "departments", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
|
||||
11
test/fixtures/comments.yml
vendored
Normal file
11
test/fixtures/comments.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
|
||||
|
||||
one:
|
||||
content: "This is a test comment"
|
||||
user: employee
|
||||
task: one
|
||||
|
||||
two:
|
||||
content: "Another test comment"
|
||||
user: manager
|
||||
task: two
|
||||
7
test/models/comment_test.rb
Normal file
7
test/models/comment_test.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
require 'test_helper'
|
||||
|
||||
class CommentTest < ActiveSupport::TestCase
|
||||
# test "the truth" do
|
||||
# assert true
|
||||
# end
|
||||
end
|
||||
33
test/system/RECOVERY_ARTIFACT.md
Normal file
33
test/system/RECOVERY_ARTIFACT.md
Normal file
@@ -0,0 +1,33 @@
|
||||
This summary outlines the evolution of our work on the Rails Todo Application, moving from an initial audit to a modernized, stabilized system with AJAX features and robust role-based access.
|
||||
|
||||
1. Initial Audit & Modernization
|
||||
Audit Findings: Identified a legacy Rails 5.1 stack with broken tests and a static UI that required full page reloads for every action.
|
||||
AJAX Implementation:
|
||||
Transformed the task completion toggle into an asynchronous action.
|
||||
Implemented dynamic partial updates via
|
||||
|
||||
update.js.erb
|
||||
, allowing the UI to reflect changes instantly without flickering.
|
||||
Test Suite Foundation: Repaired the existing (broken) tests and added new model/controller tests to ensure reliable feature verification.
|
||||
2. Dynamic Task Management
|
||||
Sorting Logic: Implemented an "Active First" sorting system. Pending tasks are kept at the top, while completed tasks are automatically moved to the bottom.
|
||||
Real-time Reordering: Updated the AJAX logic so that toggling a task's status triggers a re-render of the entire list, ensuring the correct sorting order is maintained dynamically.
|
||||
3. System Stabilization (RBAC & Schema)
|
||||
Architecture Update: Following your expansion of the codebase to include Users, Departments, and Priorities, I stabilized the logic across all layers.
|
||||
Authorization: Refactored the AuthorizationConcern into a proper Rails module to handle role-based permissions (Admin, Manager, Employee).
|
||||
Database & Reliability:
|
||||
Generated the missing TaskActivity model to support action logging.
|
||||
Updated all fixtures and test cases to account for new required fields like
|
||||
|
||||
status
|
||||
,
|
||||
|
||||
priority
|
||||
, and creator.
|
||||
Fixed critical CSS syntax errors that were breaking asset compilation and testing.
|
||||
4. Verification & Devise Support
|
||||
Browser-Based Verification: Confirmed via automated agents that the AJAX toggle and reordering worked perfectly in the browser without full-page refreshes.
|
||||
Devise Expertise: Guided you through handling the Confirmable module in development, including using the Rails console for manual confirmation and checking server logs for confirmation links.
|
||||
Final Code State
|
||||
Tests: 22/22 Passing (model, controller, and AJAX).
|
||||
Features: AJAX completion, dynamic sorting, role-based access, and department-level filtering.
|
||||
Reference in New Issue
Block a user