From 06591d00256cf1227b0a151a203a19bb9696f547 Mon Sep 17 00:00:00 2001
From: Teal Bauer <git@teal.is>
Date: Fri, 24 May 2024 11:41:28 +0200
Subject: [PATCH] Add turbo/hotwire, fix routing

---
 Gemfile                                       |  4 ++
 Gemfile.lock                                  | 16 ++++++++
 app/assets/config/manifest.js                 |  2 +
 app/controllers/assignments_controller.rb     |  8 ++++
 app/controllers/conferences_controller.rb     |  1 +
 app/controllers/sessions_controller.rb        | 40 +++++++++++++++++++
 app/helpers/assignments_helper.rb             |  2 +
 app/javascript/application.js                 |  3 ++
 app/javascript/controllers/application.js     |  9 +++++
 .../controllers/hello_controller.js           |  7 ++++
 app/javascript/controllers/index.js           | 11 +++++
 app/models/assignment.rb                      |  2 +
 app/models/conference.rb                      |  4 ++
 app/models/session.rb                         |  4 ++
 app/views/assignments/index.html.erb          | 10 +++++
 app/views/assignments/show.html.erb           |  4 ++
 app/views/conferences/show.html.erb           | 11 +++--
 app/views/layouts/application.html.erb        |  1 +
 app/views/sessions/show.html.erb              | 34 ++++++++++++++++
 bin/importmap                                 |  4 ++
 config/importmap.rb                           |  7 ++++
 config/routes.rb                              | 18 +++++++--
 ...me_format_to_session_format_in_sessions.rb |  5 +++
 db/schema.rb                                  |  4 +-
 db/seeds.rb                                   |  4 ++
 .../assignments_controller_test.rb            |  8 ++++
 vendor/javascript/.keep                       |  0
 27 files changed, 215 insertions(+), 8 deletions(-)
 create mode 100644 app/controllers/assignments_controller.rb
 create mode 100644 app/helpers/assignments_helper.rb
 create mode 100644 app/javascript/application.js
 create mode 100644 app/javascript/controllers/application.js
 create mode 100644 app/javascript/controllers/hello_controller.js
 create mode 100644 app/javascript/controllers/index.js
 create mode 100644 app/views/assignments/index.html.erb
 create mode 100644 app/views/assignments/show.html.erb
 create mode 100644 app/views/sessions/show.html.erb
 create mode 100755 bin/importmap
 create mode 100644 config/importmap.rb
 create mode 100644 db/migrate/20240524091736_rename_format_to_session_format_in_sessions.rb
 create mode 100644 test/controllers/assignments_controller_test.rb
 create mode 100644 vendor/javascript/.keep

diff --git a/Gemfile b/Gemfile
index 2712516..4717b9b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -61,3 +61,7 @@ gem "solid_queue"
 gem "httparty"
 
 gem "tailwindcss-rails", "~> 2.6"
+
+gem "hotwire-rails", "~> 0.1.3"
+
+gem "importmap-rails", "~> 2.0"
diff --git a/Gemfile.lock b/Gemfile.lock
index 6d767c6..a07fabb 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -108,11 +108,19 @@ GEM
       raabro (~> 1.4)
     globalid (1.2.1)
       activesupport (>= 6.1)
+    hotwire-rails (0.1.3)
+      rails (>= 6.0.0)
+      stimulus-rails
+      turbo-rails
     httparty (0.21.0)
       mini_mime (>= 1.0.0)
       multi_xml (>= 0.5.2)
     i18n (1.14.4)
       concurrent-ruby (~> 1.0)
+    importmap-rails (2.0.1)
+      actionpack (>= 6.0.0)
+      activesupport (>= 6.0.0)
+      railties (>= 6.0.0)
     io-console (0.7.2)
     irb (1.12.0)
       rdoc
@@ -229,6 +237,8 @@ GEM
     sqlite3 (1.7.3-aarch64-linux)
     sqlite3 (1.7.3-arm64-darwin)
     sqlite3 (1.7.3-x86_64-linux)
+    stimulus-rails (1.3.3)
+      railties (>= 6.0.0)
     stringio (3.1.0)
     strscan (3.1.0)
     tailwindcss-rails (2.6.0-aarch64-linux)
@@ -239,6 +249,10 @@ GEM
       railties (>= 7.0.0)
     thor (1.3.1)
     timeout (0.4.1)
+    turbo-rails (2.0.5)
+      actionpack (>= 6.0.0)
+      activejob (>= 6.0.0)
+      railties (>= 6.0.0)
     tzinfo (2.0.6)
       concurrent-ruby (~> 1.0)
     web-console (4.2.1)
@@ -264,7 +278,9 @@ DEPENDENCIES
   bootsnap
   capybara
   debug
+  hotwire-rails (~> 0.1.3)
   httparty
+  importmap-rails (~> 2.0)
   jbuilder
   puma (>= 5.0)
   rails (~> 7.1.2)
diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js
index 338a0e8..d39ca55 100644
--- a/app/assets/config/manifest.js
+++ b/app/assets/config/manifest.js
@@ -1,3 +1,5 @@
 //= link_tree ../images
 //= link_directory ../stylesheets .css
 //= link_tree ../builds
+//= link_tree ../../javascript .js
+//= link_tree ../../../vendor/javascript .js
diff --git a/app/controllers/assignments_controller.rb b/app/controllers/assignments_controller.rb
new file mode 100644
index 0000000..8209076
--- /dev/null
+++ b/app/controllers/assignments_controller.rb
@@ -0,0 +1,8 @@
+class AssignmentsController < ApplicationController
+  def index
+    @assignments = Assignment.all
+    if params[:user_id]
+      @assignments = @assignments.where(user_id: params[:user_id])
+    end
+  end
+end
diff --git a/app/controllers/conferences_controller.rb b/app/controllers/conferences_controller.rb
index 3614abd..038e590 100644
--- a/app/controllers/conferences_controller.rb
+++ b/app/controllers/conferences_controller.rb
@@ -7,5 +7,6 @@ class ConferencesController < ApplicationController
     @conference = Conference.find_by(slug: params[:slug])
     @sessions = @conference.sessions.where.not(starts_at: nil).includes(:stage).order(:starts_at)
     @sessions_by_date = @sessions.group_by{ |x| x.starts_at.to_date }
+    @users = User.all
   end
 end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 47fa91b..5f107b0 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -16,4 +16,44 @@ class SessionsController < ApplicationController
 
     # Further filtering options can be added here
   end
+
+  def show
+    @conference = Conference.find_by(slug: params[:slug])
+    @session = Session.includes(:stage).find_by(ref_id: params[:ref_id])
+    @users = User.all
+  end
+
+  def assign_user
+    @session = Session.find_by(ref_id: params[:ref_id])
+    @conference = Conference.find_by(slug: params[:slug])
+    @user = User.find(params[:user_id])
+    @assignment = @session.assignments.new(session: @session, user: @user)
+    @users = User.all
+
+    if @assignment.save
+      flash.now[:success] = 'User assigned successfully.'
+      respond_to do |format|
+        # format.turbo_stream
+        format.html { redirect_to conference_session_path(@session.conference, @session), success: 'User assigned successfully.' }
+      end
+    else
+      flash.now[:alert] = 'Failed to assign user.'
+      respond_to do |format|
+        # format.turbo_stream { render :show, status: :unprocessable_entity }
+        format.html { render :show, status: :unprocessable_entity, alert: 'Failed to assign user.' }
+      end
+    end
+  end
+
+  def unassign_user
+    @session = Session.find_by(ref_id: params[:ref_id])
+    @user = User.find(params[:user_id])
+    @assignment = Assignment.find_by(session: @session, user: @user)
+
+    if @assignment&.destroy
+      redirect_to conference_session_path(@session.conference, @session), notice: 'User removed successfully.'
+    else
+      redirect_to conference_session_path(@session.conference, @session), alert: 'Failed to remove user.'
+    end
+  end
 end
diff --git a/app/helpers/assignments_helper.rb b/app/helpers/assignments_helper.rb
new file mode 100644
index 0000000..6f7c33b
--- /dev/null
+++ b/app/helpers/assignments_helper.rb
@@ -0,0 +1,2 @@
+module AssignmentsHelper
+end
diff --git a/app/javascript/application.js b/app/javascript/application.js
new file mode 100644
index 0000000..e239947
--- /dev/null
+++ b/app/javascript/application.js
@@ -0,0 +1,3 @@
+// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
+import "controllers"
+import "@hotwired/turbo-rails"
diff --git a/app/javascript/controllers/application.js b/app/javascript/controllers/application.js
new file mode 100644
index 0000000..1213e85
--- /dev/null
+++ b/app/javascript/controllers/application.js
@@ -0,0 +1,9 @@
+import { Application } from "@hotwired/stimulus"
+
+const application = Application.start()
+
+// Configure Stimulus development experience
+application.debug = false
+window.Stimulus   = application
+
+export { application }
diff --git a/app/javascript/controllers/hello_controller.js b/app/javascript/controllers/hello_controller.js
new file mode 100644
index 0000000..5975c07
--- /dev/null
+++ b/app/javascript/controllers/hello_controller.js
@@ -0,0 +1,7 @@
+import { Controller } from "@hotwired/stimulus"
+
+export default class extends Controller {
+  connect() {
+    this.element.textContent = "Hello World!"
+  }
+}
diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js
new file mode 100644
index 0000000..54ad4ca
--- /dev/null
+++ b/app/javascript/controllers/index.js
@@ -0,0 +1,11 @@
+// Import and register all your controllers from the importmap under controllers/*
+
+import { application } from "controllers/application"
+
+// Eager load all controllers defined in the import map under controllers/**/*_controller
+import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
+eagerLoadControllersFrom("controllers", application)
+
+// Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
+// import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
+// lazyLoadControllersFrom("controllers", application)
diff --git a/app/models/assignment.rb b/app/models/assignment.rb
index e8bb8a3..b6db132 100644
--- a/app/models/assignment.rb
+++ b/app/models/assignment.rb
@@ -1,4 +1,6 @@
 class Assignment < ApplicationRecord
   belongs_to :user
   belongs_to :session
+
+  validates :user_id, uniqueness: { scope: :session_id, message: "has already been assigned to this session" }
 end
diff --git a/app/models/conference.rb b/app/models/conference.rb
index 07e4145..40b40a5 100644
--- a/app/models/conference.rb
+++ b/app/models/conference.rb
@@ -6,4 +6,8 @@ class Conference < ApplicationRecord
   def days
     (starts_at.to_date..ends_at.to_date)
   end
+
+  def to_param
+    slug
+  end
 end
diff --git a/app/models/session.rb b/app/models/session.rb
index 687e226..6a18ee7 100644
--- a/app/models/session.rb
+++ b/app/models/session.rb
@@ -5,4 +5,8 @@ class Session < ApplicationRecord
   has_many :users, through: :assignments
 
   validates :ref_id, uniqueness: { scope: :conference_id }
+
+  def to_param
+    ref_id
+  end
 end
diff --git a/app/views/assignments/index.html.erb b/app/views/assignments/index.html.erb
new file mode 100644
index 0000000..139f949
--- /dev/null
+++ b/app/views/assignments/index.html.erb
@@ -0,0 +1,10 @@
+<div>
+  <% @assignments.group_by(&:user).each do |user, assignments| %>
+  <h4><%= user.name %></h4>
+  <ul>
+  <% assignments.each do |assignment| %>
+    <li><%= link_to assignment.session.title, assignment.session %></li>
+  <% end %>
+  </ul>
+  <% end %>
+</div>
\ No newline at end of file
diff --git a/app/views/assignments/show.html.erb b/app/views/assignments/show.html.erb
new file mode 100644
index 0000000..6c53075
--- /dev/null
+++ b/app/views/assignments/show.html.erb
@@ -0,0 +1,4 @@
+<div>
+  <h1 class="font-bold text-4xl">Assignments#show</h1>
+  <p>Find me in app/views/assignments/show.html.erb</p>
+</div>
diff --git a/app/views/conferences/show.html.erb b/app/views/conferences/show.html.erb
index 3c73243..911fe41 100644
--- a/app/views/conferences/show.html.erb
+++ b/app/views/conferences/show.html.erb
@@ -52,10 +52,15 @@ current_time = @sessions_by_date[@conference.days.first].first.starts_at.advance
           <h4><%= stage.name %></h4>
           <div class="stage-sessions">
           <% sessions.each do |session| %>
-            <div class="session" style="position: absolute; top: <%= (session.starts_at - timeline_starts_at) / 3600.0 * pixels_per_hour %>px; height: <%= (session.ends_at - session.starts_at) / 3600.0 * pixels_per_hour%>px;">
-              <h4><%= session.title %></h4>
+            <div class="session" style="position: absolute; top: <%= (session.starts_at - timeline_starts_at) / 3600.0 * pixels_per_hour %>px; height: <%= (session.ends_at - session.starts_at) / 3600.0 * pixels_per_hour%>px; overflow: scroll;">
+              <h4><%= link_to session.title, [@conference, session] %></h4>
               <p class="session-time"><%= session.starts_at.strftime('%H:%M') %> - <%= session.ends_at.strftime('%H:%M') %></p>
-              <div class="session-desc"><%= session.description.html_safe %></div>
+              <%#<div class="session-desc"><%= session.description.html_safe %><%#/div>%>
+              <%= form_with url: assign_user_session_path(@conference, session), method: :post, local: true do |f| %>
+                <%= f.label :user_id, "Assign User" %>
+                <%= f.select :user_id, options_from_collection_for_select(@users, :id, :name) %>
+                <%= f.submit "Assign" %>
+              <% end %>
             </div>
           <% end %>
           </div>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index f02f8a2..9f79c2f 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -8,6 +8,7 @@
     <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
 
     <%= stylesheet_link_tag "application" %>
+    <%= javascript_importmap_tags %>
   </head>
 
   <body>
diff --git a/app/views/sessions/show.html.erb b/app/views/sessions/show.html.erb
new file mode 100644
index 0000000..32b19c4
--- /dev/null
+++ b/app/views/sessions/show.html.erb
@@ -0,0 +1,34 @@
+<div>
+  <h6><%= @session.conference.name %></h6>
+  <h1><%= @session.title %></h1>
+  <h2><%= @session.language %> <%= @session.is_interpreted ? "(live interpretation)" : "" %></h2>
+  <h2><%= @session.starts_at.strftime("%Y-%m-%d") %> <%= @session.starts_at.strftime("%H:%M") %> &ndash; <%= @session.ends_at.strftime("%H:%M") %></h2>
+  <h2><%= @session.stage.name %>&middot; <%= @session.session_format %> &middot; <%= @session.track %></h2>
+  <p><%= @session.description.html_safe %></p>
+  
+  <h3>Assigned Users</h3>
+  <ul>
+    <% @session.users.each do |user| %>
+      <li>
+        <%= user.name %>
+        <%= link_to '[Remove]', unassign_user_session_path([@session.conference, @session], user_id: user.id), data: { turbo_method: :delete, confirm: 'Are you sure?' } %>
+      </li>
+    <% end %>
+  </ul>
+
+  <%= form_with url: assign_user_session_path([@session.conference, @session]), method: :post, local: true do |f| %>
+    <%= f.label :user_id, "Assign User" %>
+    <%= f.select :user_id, options_from_collection_for_select(@users, :id, :name) %>
+    <%= f.submit "Assign" %>
+    <div id="flash">
+      <% flash.each do |key, value| %>
+        <div class="alert alert-<%= key %>"><%= value %></div>
+      <% end %>
+    </div>
+    <% if @assignment&.errors&.any? %>
+      <div class="alert alert-danger">
+        <%= @assignment.errors.full_messages.join(", ") %>
+      </div>
+    <% end %>
+  <% end %>
+</div>
\ No newline at end of file
diff --git a/bin/importmap b/bin/importmap
new file mode 100755
index 0000000..36502ab
--- /dev/null
+++ b/bin/importmap
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+
+require_relative "../config/application"
+require "importmap/commands"
diff --git a/config/importmap.rb b/config/importmap.rb
new file mode 100644
index 0000000..cb0480c
--- /dev/null
+++ b/config/importmap.rb
@@ -0,0 +1,7 @@
+# Pin npm packages by running ./bin/importmap
+
+pin "application"
+pin "@hotwired/stimulus", to: "stimulus.min.js"
+pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
+pin_all_from "app/javascript/controllers", under: "controllers"
+pin "@hotwired/turbo-rails", to: "turbo.min.js"
diff --git a/config/routes.rb b/config/routes.rb
index a6369e4..2521790 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,5 +1,4 @@
 Rails.application.routes.draw do
-  get 'sessions/index'
   # 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.
@@ -10,7 +9,20 @@ Rails.application.routes.draw do
   # root "posts#index"
   root "conferences#index"
 
-  resources :conferences, path: '/', param: :slug do
-    resources :sessions, only: [:index]
+  resources :conferences, param: :slug do
+    resources :sessions do
+      resource :assigments
+    end
   end
+
+  resources :assignments
+  resources :sessions, param: :ref_id
+
+  # get 'conferences/:slug', to: 'conferences#show', as: :conference
+  # get 'conferences/:slug/:date', to: 'conferences#show', as: :conference_day
+  # get 'conferences/:slug/session/:ref_id', to: 'sessions#show', as: :conference_session
+  # post 'conferences/:slug/session/:ref_id/assignments', to: 'sessions#assign_user', as: :assign_user_session
+  # delete 'conferences/:slug/session/:ref_id/assignments', to: 'sessions#unassign_user', as: :unassign_user_session
+  # get 'assignments', to: 'assignments#index', as: :assignments
+  # get 'assignments/:user_id', to: 'assignments#index', as: :user_assignments
 end
diff --git a/db/migrate/20240524091736_rename_format_to_session_format_in_sessions.rb b/db/migrate/20240524091736_rename_format_to_session_format_in_sessions.rb
new file mode 100644
index 0000000..3bc825c
--- /dev/null
+++ b/db/migrate/20240524091736_rename_format_to_session_format_in_sessions.rb
@@ -0,0 +1,5 @@
+class RenameFormatToSessionFormatInSessions < ActiveRecord::Migration[7.1]
+  def change
+    rename_column :sessions, :format, :session_format
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index cb01004..eff275d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema[7.1].define(version: 2024_04_06_184312) do
+ActiveRecord::Schema[7.1].define(version: 2024_05_24_091736) do
   create_table "assignments", force: :cascade do |t|
     t.integer "user_id", null: false
     t.integer "session_id", null: false
@@ -39,7 +39,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_04_06_184312) do
     t.string "language"
     t.string "status"
     t.text "description"
-    t.string "format"
+    t.string "session_format"
     t.string "track"
     t.boolean "is_interpreted"
     t.datetime "starts_at"
diff --git a/db/seeds.rb b/db/seeds.rb
index 42df632..766011b 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -29,3 +29,7 @@ Conference.find_or_create_by!(slug: "rp2024") do |c|
   }
   c.import_job_class = "republica_2023_or_later"
 end
+
+User.find_or_create_by!(email: "teal@teal.is") do |user|
+  user.name = "Teal"
+end
diff --git a/test/controllers/assignments_controller_test.rb b/test/controllers/assignments_controller_test.rb
new file mode 100644
index 0000000..e733d3a
--- /dev/null
+++ b/test/controllers/assignments_controller_test.rb
@@ -0,0 +1,8 @@
+require "test_helper"
+
+class AssignmentsControllerTest < ActionDispatch::IntegrationTest
+  test "should get show" do
+    get assignments_show_url
+    assert_response :success
+  end
+end
diff --git a/vendor/javascript/.keep b/vendor/javascript/.keep
new file mode 100644
index 0000000..e69de29
-- 
GitLab