diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 83610cf..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: 2 -updates: -- package-ecosystem: bundler - directory: "/" - schedule: - interval: weekly - open-pull-requests-limit: 10 -- package-ecosystem: github-actions - directory: "/" - schedule: - interval: weekly - open-pull-requests-limit: 10 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 6824190..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,124 +0,0 @@ -name: CI - -on: - pull_request: - push: - branches: [ main ] - -jobs: - scan_ruby: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - - name: Scan for common Rails security vulnerabilities using static analysis - run: bin/brakeman --no-pager - - - name: Scan for known security vulnerabilities in gems used - run: bin/bundler-audit - - scan_js: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - - name: Scan for security vulnerabilities in JavaScript dependencies - run: bin/importmap audit - - lint: - runs-on: ubuntu-latest - env: - RUBOCOP_CACHE_ROOT: tmp/rubocop - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - - name: Prepare RuboCop cache - uses: actions/cache@v4 - env: - DEPENDENCIES_HASH: ${{ hashFiles('.ruby-version', '**/.rubocop.yml', '**/.rubocop_todo.yml', 'Gemfile.lock') }} - with: - path: ${{ env.RUBOCOP_CACHE_ROOT }} - key: rubocop-${{ runner.os }}-${{ env.DEPENDENCIES_HASH }}-${{ github.ref_name == github.event.repository.default_branch && github.run_id || 'default' }} - restore-keys: | - rubocop-${{ runner.os }}-${{ env.DEPENDENCIES_HASH }}- - - - name: Lint code for consistent style - run: bin/rubocop -f github - - test: - runs-on: ubuntu-latest - - # services: - # redis: - # image: valkey/valkey:8 - # ports: - # - 6379:6379 - # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - - name: Run tests - env: - RAILS_ENV: test - # RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} - # REDIS_URL: redis://localhost:6379/0 - run: bin/rails db:test:prepare test - - system-test: - runs-on: ubuntu-latest - - # services: - # redis: - # image: valkey/valkey:8 - # ports: - # - 6379:6379 - # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - - name: Run System Tests - env: - RAILS_ENV: test - # RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} - # REDIS_URL: redis://localhost:6379/0 - run: bin/rails db:test:prepare test:system - - - name: Keep screenshots from failed system tests - uses: actions/upload-artifact@v4 - if: failure() - with: - name: screenshots - path: ${{ github.workspace }}/tmp/screenshots - if-no-files-found: ignore diff --git a/.kamal/hooks/docker-setup.sample b/.kamal/hooks/docker-setup.sample deleted file mode 100755 index 2fb07d7..0000000 --- a/.kamal/hooks/docker-setup.sample +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -echo "Docker set up on $KAMAL_HOSTS..." diff --git a/.kamal/hooks/post-app-boot.sample b/.kamal/hooks/post-app-boot.sample deleted file mode 100755 index 70f9c4b..0000000 --- a/.kamal/hooks/post-app-boot.sample +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..." diff --git a/.kamal/hooks/post-deploy.sample b/.kamal/hooks/post-deploy.sample deleted file mode 100755 index fd364c2..0000000 --- a/.kamal/hooks/post-deploy.sample +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -# A sample post-deploy hook -# -# These environment variables are available: -# KAMAL_RECORDED_AT -# KAMAL_PERFORMER -# KAMAL_VERSION -# KAMAL_HOSTS -# KAMAL_ROLES (if set) -# KAMAL_DESTINATION (if set) -# KAMAL_RUNTIME - -echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds" diff --git a/.kamal/hooks/post-proxy-reboot.sample b/.kamal/hooks/post-proxy-reboot.sample deleted file mode 100755 index 1435a67..0000000 --- a/.kamal/hooks/post-proxy-reboot.sample +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -echo "Rebooted kamal-proxy on $KAMAL_HOSTS" diff --git a/.kamal/hooks/pre-app-boot.sample b/.kamal/hooks/pre-app-boot.sample deleted file mode 100755 index 45f7355..0000000 --- a/.kamal/hooks/pre-app-boot.sample +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..." diff --git a/.kamal/hooks/pre-build.sample b/.kamal/hooks/pre-build.sample deleted file mode 100755 index c5a5567..0000000 --- a/.kamal/hooks/pre-build.sample +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/sh - -# A sample pre-build hook -# -# Checks: -# 1. We have a clean checkout -# 2. A remote is configured -# 3. The branch has been pushed to the remote -# 4. The version we are deploying matches the remote -# -# These environment variables are available: -# KAMAL_RECORDED_AT -# KAMAL_PERFORMER -# KAMAL_VERSION -# KAMAL_HOSTS -# KAMAL_ROLES (if set) -# KAMAL_DESTINATION (if set) - -if [ -n "$(git status --porcelain)" ]; then - echo "Git checkout is not clean, aborting..." >&2 - git status --porcelain >&2 - exit 1 -fi - -first_remote=$(git remote) - -if [ -z "$first_remote" ]; then - echo "No git remote set, aborting..." >&2 - exit 1 -fi - -current_branch=$(git branch --show-current) - -if [ -z "$current_branch" ]; then - echo "Not on a git branch, aborting..." >&2 - exit 1 -fi - -remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1) - -if [ -z "$remote_head" ]; then - echo "Branch not pushed to remote, aborting..." >&2 - exit 1 -fi - -if [ "$KAMAL_VERSION" != "$remote_head" ]; then - echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2 - exit 1 -fi - -exit 0 diff --git a/.kamal/hooks/pre-connect.sample b/.kamal/hooks/pre-connect.sample deleted file mode 100755 index 77744bd..0000000 --- a/.kamal/hooks/pre-connect.sample +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env ruby - -# A sample pre-connect check -# -# Warms DNS before connecting to hosts in parallel -# -# These environment variables are available: -# KAMAL_RECORDED_AT -# KAMAL_PERFORMER -# KAMAL_VERSION -# KAMAL_HOSTS -# KAMAL_ROLES (if set) -# KAMAL_DESTINATION (if set) -# KAMAL_RUNTIME - -hosts = ENV["KAMAL_HOSTS"].split(",") -results = nil -max = 3 - -elapsed = Benchmark.realtime do - results = hosts.map do |host| - Thread.new do - tries = 1 - - begin - Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) - rescue SocketError - if tries < max - puts "Retrying DNS warmup: #{host}" - tries += 1 - sleep rand - retry - else - puts "DNS warmup failed: #{host}" - host - end - end - - tries - end - end.map(&:value) -end - -retries = results.sum - hosts.size -nopes = results.count { |r| r == max } - -puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ] diff --git a/.kamal/hooks/pre-deploy.sample b/.kamal/hooks/pre-deploy.sample deleted file mode 100755 index 05b3055..0000000 --- a/.kamal/hooks/pre-deploy.sample +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env ruby - -# A sample pre-deploy hook -# -# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds. -# -# Fails unless the combined status is "success" -# -# These environment variables are available: -# KAMAL_RECORDED_AT -# KAMAL_PERFORMER -# KAMAL_VERSION -# KAMAL_HOSTS -# KAMAL_COMMAND -# KAMAL_SUBCOMMAND -# KAMAL_ROLES (if set) -# KAMAL_DESTINATION (if set) - -# Only check the build status for production deployments -if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production" - exit 0 -end - -require "bundler/inline" - -# true = install gems so this is fast on repeat invocations -gemfile(true, quiet: true) do - source "https://rubygems.org" - - gem "octokit" - gem "faraday-retry" -end - -MAX_ATTEMPTS = 72 -ATTEMPTS_GAP = 10 - -def exit_with_error(message) - $stderr.puts message - exit 1 -end - -class GithubStatusChecks - attr_reader :remote_url, :git_sha, :github_client, :combined_status - - def initialize - @remote_url = github_repo_from_remote_url - @git_sha = `git rev-parse HEAD`.strip - @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"]) - refresh! - end - - def refresh! - @combined_status = github_client.combined_status(remote_url, git_sha) - end - - def state - combined_status[:state] - end - - def first_status_url - first_status = combined_status[:statuses].find { |status| status[:state] == state } - first_status && first_status[:target_url] - end - - def complete_count - combined_status[:statuses].count { |status| status[:state] != "pending"} - end - - def total_count - combined_status[:statuses].count - end - - def current_status - if total_count > 0 - "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..." - else - "Build not started..." - end - end - - private - def github_repo_from_remote_url - url = `git config --get remote.origin.url`.strip.delete_suffix(".git") - if url.start_with?("https://github.com/") - url.delete_prefix("https://github.com/") - elsif url.start_with?("git@github.com:") - url.delete_prefix("git@github.com:") - else - url - end - end -end - - -$stdout.sync = true - -begin - puts "Checking build status..." - - attempts = 0 - checks = GithubStatusChecks.new - - loop do - case checks.state - when "success" - puts "Checks passed, see #{checks.first_status_url}" - exit 0 - when "failure" - exit_with_error "Checks failed, see #{checks.first_status_url}" - when "pending" - attempts += 1 - end - - exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS - - puts checks.current_status - sleep(ATTEMPTS_GAP) - checks.refresh! - end -rescue Octokit::NotFound - exit_with_error "Build status could not be found" -end diff --git a/.kamal/hooks/pre-proxy-reboot.sample b/.kamal/hooks/pre-proxy-reboot.sample deleted file mode 100755 index 061f805..0000000 --- a/.kamal/hooks/pre-proxy-reboot.sample +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -echo "Rebooting kamal-proxy on $KAMAL_HOSTS..." diff --git a/.kamal/secrets b/.kamal/secrets deleted file mode 100644 index b3089d6..0000000 --- a/.kamal/secrets +++ /dev/null @@ -1,20 +0,0 @@ -# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets, -# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either -# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. - -# Example of extracting secrets from 1password (or another compatible pw manager) -# SECRETS=$(kamal secrets fetch --adapter 1password --account your-account --from Vault/Item KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY) -# KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD ${SECRETS}) -# RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY ${SECRETS}) - -# Example of extracting secrets from Rails credentials -# KAMAL_REGISTRY_PASSWORD=$(rails credentials:fetch kamal.registry_password) - -# Use a GITHUB_TOKEN if private repositories are needed for the image -# GITHUB_TOKEN=$(gh config get -h github.com oauth_token) - -# Grab the registry password from ENV -# KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD - -# Improve security by using a password manager. Never check config/master.key into git! -RAILS_MASTER_KEY=$(cat config/master.key) diff --git a/Gemfile b/Gemfile index c4aca79..9a38f1f 100644 --- a/Gemfile +++ b/Gemfile @@ -64,3 +64,5 @@ group :test do gem "capybara" gem "selenium-webdriver" end + +gem "tailwindcss", "~> 0.1.1" diff --git a/Gemfile.lock b/Gemfile.lock index 6700a33..a486f73 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -351,6 +351,7 @@ GEM stimulus-rails (1.3.4) railties (>= 6.0.0) stringio (3.2.0) + tailwindcss (0.1.1) thor (1.5.0) thruster (0.1.17) thruster (0.1.17-aarch64-linux) @@ -415,6 +416,7 @@ DEPENDENCIES solid_queue sqlite3 (>= 2.1) stimulus-rails + tailwindcss (~> 0.1.1) thruster turbo-rails tzinfo-data @@ -552,6 +554,7 @@ CHECKSUMS sshkit (1.25.0) sha256=c8c6543cdb60f91f1d277306d585dd11b6a064cb44eab0972827e4311ff96744 stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06 stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 + tailwindcss (0.1.1) sha256=cdd453b13267d6441cba86cc6bf2b91e06bf01171983d90701b8d9b634d3be75 thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 thruster (0.1.17) sha256=6f3f1de43e22f0162d81cbc363f45ee42a1b8460213856c1a899cbf0d3297235 thruster (0.1.17-aarch64-linux) sha256=1b3a34b2814185c2aeaf835b5ecff5348cdcf8e77809f7a092d46e4b962a16ba diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index fe93333..54cec86 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -1,10 +1,32 @@ -/* - * This is a manifest file that'll be compiled into application.css. - * - * With Propshaft, assets are served efficiently without preprocessing steps. You can still include - * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard - * cascading order, meaning styles declared later in the document or manifest will override earlier ones, - * depending on specificity. - * - * Consider organizing styles into separate files for maintainability. - */ +/* application.css */ + +body { + font-family: sans-serif; + background-color: #f9f9f9; + margin: 18px; + padding: 0; +} + +li { + border: 1px solid #ddd; + border-radius: 8px; + padding: 10px; + margin-bottom: 10px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.task-actions { + display: flex; + gap: 8px; +} + +.done-task { + background-color: #f0f0f0; + color: #888; +} + +.done-task .task-title { + text-decoration: line-through; +} \ No newline at end of file diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb new file mode 100644 index 0000000..3cbf145 --- /dev/null +++ b/app/controllers/projects_controller.rb @@ -0,0 +1,48 @@ +class ProjectsController < ApplicationController + def index + @projects = Project.all + end + + def show + @project = Project.find(params[:id]) + end + + def new + @project = Project.new + end + + def create + @project = Project.new(project_params) + if @project.save + redirect_to @project + else + render :new + end + end + + def edit + @project = Project.find(params[:id]) + end + + def update + @project = Project.find(params[:id]) + if @project.update(project_params) + redirect_to @project + else + render :edit + end + end + + def destroy + @project = Project.find(params[:id]) + @project.destroy + redirect_to projects_path + end + + + private + + def project_params + params.require(:project).permit(:project_name, :description) + end +end diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb new file mode 100644 index 0000000..b4d7211 --- /dev/null +++ b/app/controllers/tasks_controller.rb @@ -0,0 +1,53 @@ +class TasksController < ApplicationController + before_action :set_project + + def create + @task = @project.tasks.build(task_params) + + if @task.save + respond_to do |format| + format.turbo_stream + format.html { redirect_to @project, notice: "Task created!" } + end + else + respond_to do |format| + format.turbo_stream { render turbo_stream: turbo_stream.replace("new_task", partial: "tasks/form", locals: { project: @project, task: @task }) } + format.html { render "projects/show", status: :unprocessable_entity } + end + end + end + + def destroy + @task = @project.tasks.find_by(id: params[:id]) + return unless @task + + task_dom_id = "task_#{@task.id}" + @task.destroy + + respond_to do |format| + format.turbo_stream { render turbo_stream: turbo_stream.remove(task_dom_id) } + format.html { redirect_to @project } + end + end + + + def complete + @task = @project.tasks.find(params[:id]) + @task.update(status: "done") + + respond_to do |format| + format.turbo_stream + format.html { redirect_to @project } + end + end + + private + + def set_project + @project = Project.find(params[:project_id]) + end + + def task_params + params.require(:task).permit(:title, :status) + end +end diff --git a/app/helpers/articles_helper.rb b/app/helpers/articles_helper.rb new file mode 100644 index 0000000..2968277 --- /dev/null +++ b/app/helpers/articles_helper.rb @@ -0,0 +1,2 @@ +module ArticlesHelper +end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb new file mode 100644 index 0000000..db5c5ce --- /dev/null +++ b/app/helpers/projects_helper.rb @@ -0,0 +1,2 @@ +module ProjectsHelper +end diff --git a/app/helpers/tasks_helper.rb b/app/helpers/tasks_helper.rb new file mode 100644 index 0000000..ce894d0 --- /dev/null +++ b/app/helpers/tasks_helper.rb @@ -0,0 +1,2 @@ +module TasksHelper +end diff --git a/app/models/project.rb b/app/models/project.rb new file mode 100644 index 0000000..1259445 --- /dev/null +++ b/app/models/project.rb @@ -0,0 +1,5 @@ +class Project < ApplicationRecord + has_many :tasks, dependent: :destroy + + validates :project_name, :description, presence: true +end diff --git a/app/models/task.rb b/app/models/task.rb new file mode 100644 index 0000000..5f0de52 --- /dev/null +++ b/app/models/task.rb @@ -0,0 +1,7 @@ +class Task < ApplicationRecord + belongs_to :project + + enum :status, { to_do: 0, in_progress: 1, done: 2 } + + validates :title, presence: true +end diff --git a/app/views/articles/index.html.erb b/app/views/articles/index.html.erb new file mode 100644 index 0000000..ab9c552 --- /dev/null +++ b/app/views/articles/index.html.erb @@ -0,0 +1,2 @@ +

Articles#index

+

Find me in app/views/articles/index.html.erb

diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb new file mode 100644 index 0000000..071eb3a --- /dev/null +++ b/app/views/projects/index.html.erb @@ -0,0 +1,35 @@ +

Projects

+ +
+ +
+ +
+

<%= link_to "Add Project", new_project_path %>

diff --git a/app/views/projects/new.html.erb b/app/views/projects/new.html.erb new file mode 100644 index 0000000..62a2795 --- /dev/null +++ b/app/views/projects/new.html.erb @@ -0,0 +1,23 @@ +

New Project

+ +<%= form_with model: @project do |form| %> +
+ <%= form.label :project_name %>
+ <%= form.text_field :project_name %> + <% @project.errors.full_messages_for(:project_name).each do |message| %> +

<%= message %>

+ <% end %> +
+ +
+ <%= form.label :description %>
+ <%= form.text_area :description %> + <% @project.errors.full_messages_for(:description).each do |message| %> +

<%= message %>

+ <% end %> +
+ +
+ <%= form.submit %> +
+<% end %> \ No newline at end of file diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb new file mode 100644 index 0000000..54609b0 --- /dev/null +++ b/app/views/projects/show.html.erb @@ -0,0 +1,22 @@ +

<%= @project.project_name %>

+

<%= @project.description %>

+ +<%= turbo_frame_tag "tasks" do %> + + + + + +<% end %> + +<%= turbo_frame_tag "new_task" do %> + <%= render "tasks/form", project: @project, task: Task.new %> +<% end %> diff --git a/app/views/tasks/_form.html.erb b/app/views/tasks/_form.html.erb new file mode 100644 index 0000000..a6a22ea --- /dev/null +++ b/app/views/tasks/_form.html.erb @@ -0,0 +1,15 @@ +

Add New Task

+ +<%= form_with model: [project, task], local: false do |f| %> +

+ <%= f.label :title %>
+ <%= f.text_field :title %> +

+ +

+ <%= f.label :status %>
+ <%= f.select :status, Task.statuses.keys.reject { |s| s == "done" } %> +

+ + <%= f.submit "Create Task" %> +<% end %> diff --git a/app/views/tasks/_task.html.erb b/app/views/tasks/_task.html.erb new file mode 100644 index 0000000..efd7312 --- /dev/null +++ b/app/views/tasks/_task.html.erb @@ -0,0 +1,18 @@ +
  • +
    <%= task.title %>
    +
    <%= task.status.titleize %>
    +
    + <% unless task.status == "done" %> + <%= button_to "Complete", + complete_project_task_path(task.project, task), + method: :patch, + data: { turbo_frame: dom_id(task) }, + class: "task-btn complete-btn" %> + <% end %> + + <%= link_to "Destroy", + project_task_path(task.project, task), + data: { turbo_method: :delete, turbo_confirm: "Are you sure?" }, + class: "task-btn destroy-btn" %> +
    +
  • diff --git a/app/views/tasks/complete.turbo_stream.erb b/app/views/tasks/complete.turbo_stream.erb new file mode 100644 index 0000000..7b6386f --- /dev/null +++ b/app/views/tasks/complete.turbo_stream.erb @@ -0,0 +1,3 @@ +<%= turbo_stream.replace dom_id(@task) do %> + <%= render partial: "tasks/task", locals: { task: @task } %> +<% end %> diff --git a/app/views/tasks/create.turbo_stream.erb b/app/views/tasks/create.turbo_stream.erb new file mode 100644 index 0000000..67cfd7f --- /dev/null +++ b/app/views/tasks/create.turbo_stream.erb @@ -0,0 +1,7 @@ +<%= turbo_stream.prepend "active_tasks" do %> + <%= render partial: "tasks/task", locals: { task: @task } %> + <% end %> + +<%= turbo_stream.replace "new_task" do %> + <%= render "tasks/form", project: @project, task: Task.new %> +<% end %> diff --git a/app/views/tasks/destroy.turbo_stream.erb b/app/views/tasks/destroy.turbo_stream.erb new file mode 100644 index 0000000..84da1cb --- /dev/null +++ b/app/views/tasks/destroy.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.remove dom_id(@task) %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 48254e8..fa9fbf6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,17 @@ Rails.application.routes.draw do + + root "projects#index" + + resources :projects do + resources :tasks do + member do + patch :complete + end + end + end + + + # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. diff --git a/db/migrate/20260126072711_create_projects.rb b/db/migrate/20260126072711_create_projects.rb new file mode 100644 index 0000000..fdfd571 --- /dev/null +++ b/db/migrate/20260126072711_create_projects.rb @@ -0,0 +1,9 @@ +class CreateProjects < ActiveRecord::Migration[8.1] + def change + create_table :projects do |t| + t.string :project_name + + t.timestamps + end + end +end diff --git a/db/migrate/20260126072814_create_tasks.rb b/db/migrate/20260126072814_create_tasks.rb new file mode 100644 index 0000000..8ca7137 --- /dev/null +++ b/db/migrate/20260126072814_create_tasks.rb @@ -0,0 +1,10 @@ +class CreateTasks < ActiveRecord::Migration[8.1] + def change + create_table :tasks do |t| + t.string :title + t.string :status + + t.timestamps + end + end +end diff --git a/db/migrate/20260126082816_add_description_to_projects.rb b/db/migrate/20260126082816_add_description_to_projects.rb new file mode 100644 index 0000000..2692093 --- /dev/null +++ b/db/migrate/20260126082816_add_description_to_projects.rb @@ -0,0 +1,5 @@ +class AddDescriptionToProjects < ActiveRecord::Migration[8.1] + def change + add_column :projects, :description, :string + end +end diff --git a/db/migrate/20260126091632_change_status_type_in_tasks.rb b/db/migrate/20260126091632_change_status_type_in_tasks.rb new file mode 100644 index 0000000..5ac5381 --- /dev/null +++ b/db/migrate/20260126091632_change_status_type_in_tasks.rb @@ -0,0 +1,6 @@ +class ChangeStatusTypeInTasks < ActiveRecord::Migration[8.1] + def change + remove_column :tasks, :status, :string + add_column :tasks, :status, :integer, default: 0, null: false + end +end diff --git a/db/migrate/20260126094649_add_project_to_tasks.rb b/db/migrate/20260126094649_add_project_to_tasks.rb new file mode 100644 index 0000000..031681a --- /dev/null +++ b/db/migrate/20260126094649_add_project_to_tasks.rb @@ -0,0 +1,5 @@ +class AddProjectToTasks < ActiveRecord::Migration[8.1] + def change + add_reference :tasks, :project, null: false, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..2ea0ef3 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,31 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[8.1].define(version: 2026_01_26_094649) do + create_table "projects", force: :cascade do |t| + t.datetime "created_at", null: false + t.string "description" + t.string "project_name" + t.datetime "updated_at", null: false + end + + create_table "tasks", force: :cascade do |t| + t.datetime "created_at", null: false + t.integer "project_id", null: false + t.integer "status", default: 0, null: false + t.string "title" + t.datetime "updated_at", null: false + t.index ["project_id"], name: "index_tasks_on_project_id" + end + + add_foreign_key "tasks", "projects" +end diff --git a/db/seeds.rb b/db/seeds.rb index 4fbd6ed..24e2157 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -7,3 +7,43 @@ # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| # MovieGenre.find_or_create_by!(name: genre_name) # end +# db/seeds.rb + +Task.destroy_all +Project.destroy_all + +project1 = Project.create!( + project_name: "project one", + description: "This is project one" +) + +project2 = Project.create!( + project_name: "project two", + description: "This is project two" +) + +Task.create!( + title: "first task of project one", + status: :to_do, + project: project1 +) + +Task.create!( + title: "second task of project one", + status: :in_progress, + project: project1 +) + +Task.create!( + title: "third task of project one", + status: :done, + project: project1 +) + +Task.create!( + title: "first task of project two", + status: :to_do, + project: project2 +) + +puts "Seeding complete" \ No newline at end of file diff --git a/test/controllers/projects_controller_test.rb b/test/controllers/projects_controller_test.rb new file mode 100644 index 0000000..4305777 --- /dev/null +++ b/test/controllers/projects_controller_test.rb @@ -0,0 +1,8 @@ +require "test_helper" + +class ProjectsControllerTest < ActionDispatch::IntegrationTest + test "should get index" do + get projects_index_url + assert_response :success + end +end diff --git a/test/controllers/tasks_controller_test.rb b/test/controllers/tasks_controller_test.rb new file mode 100644 index 0000000..a60ed4a --- /dev/null +++ b/test/controllers/tasks_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class TasksControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/fixtures/projects.yml b/test/fixtures/projects.yml new file mode 100644 index 0000000..0f9d978 --- /dev/null +++ b/test/fixtures/projects.yml @@ -0,0 +1,7 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + project_name: MyString + +two: + project_name: MyString diff --git a/test/fixtures/tasks.yml b/test/fixtures/tasks.yml new file mode 100644 index 0000000..e41d61c --- /dev/null +++ b/test/fixtures/tasks.yml @@ -0,0 +1,9 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + title: MyString + status: MyString + +two: + title: MyString + status: MyString diff --git a/test/models/project_test.rb b/test/models/project_test.rb new file mode 100644 index 0000000..5df4ca4 --- /dev/null +++ b/test/models/project_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class ProjectTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/task_test.rb b/test/models/task_test.rb new file mode 100644 index 0000000..29982eb --- /dev/null +++ b/test/models/task_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class TaskTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end