Commit b87ba625 authored by Zac Wood's avatar Zac Wood

Use postgres DB in production.

Added share link on schedule page.
parent 9a909447
......@@ -11,5 +11,5 @@ ENV RAILS_ENV production
RUN bundle install
RUN export SECRET_KEY_BASE=$(rails secret)
RUN rake assets:precompile
RUN rails db:migrate
RUN rails db:seed
#RUN rails db:migrate
#RUN rails db:seed
......@@ -9,6 +9,7 @@ end
gem 'rails', '5.1.6.1'
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
gem 'pg'
# Use Puma as the app server
gem 'puma', '~> 3.7'
# Use SCSS for stylesheets
......
......@@ -101,6 +101,7 @@ GEM
parallel (1.12.1)
parser (2.5.3.0)
ast (~> 2.4.0)
pg (1.1.3)
powerpack (0.1.2)
pry (0.12.2)
coderay (~> 1.1.0)
......@@ -216,6 +217,7 @@ DEPENDENCIES
listen (>= 3.0.5, < 3.2)
maruku
nokogiri
pg
pry
pry-doc
puma (~> 3.7)
......
......@@ -52,4 +52,6 @@ const initListeners = () => {
document.getElementById('open-modal-btn').onclick = setUrlInModal;
document.getElementById('download-ics').onclick = downloadIcs;
document.getElementById('add-to-system').onclick = addToSystemCalendar;
document.getElementById('share-url').innerText = `${window.location.protocol}//${window.location.hostname}/schedule/view?section_ids=${window.cart._courses.join(',')}`;
};
document.addEventListener('DOMContentLoaded', () => {
const eventsTemplate = document.querySelector('#events');
if (eventsTemplate) {
const eventsJSON = eventsTemplate.dataset.events;
const events = JSON.parse(eventsJSON);
window.events = events;
$('#calendar').fullCalendar({
defaultDate: new Date(2019, 0, 14),
defaultView: 'agendaWeek',
header: false,
events: renderEvents,
columnHeaderFormat: 'dddd',
allDaySlot: false,
});
}
initListeners();
});
const renderEvents = (start, end, timezone, callback) => {
callback(window.events);
};
/**
* Generates a URL for the current sections in the schedule
* and sets the link in the modal to it.
*/
const setUrlInModal = () => {
document.getElementById('calendar-link').innerText = `${window.location.protocol}//${window.location.hostname}/api/schedules?section_ids=${window.cart._courses.join(',')}`;
};
const downloadIcs = async () => {
const response = await fetch(`${window.location.protocol}//${window.location.hostname}/api/schedules?section_ids=${window.cart._courses.join(',')}`);
const text = await response.text();
const blob = new Blob([text], { type: 'text/calendar;charset=utf-8' });
saveAs(blob, 'GMU Schedule.ics');
};
const addToSystemCalendar = () => {
window.open(`webcal://${window.location.hostname}/api/schedules?section_ids=${window.cart._courses.join(',')}`);
};
const initListeners = () => {
document.getElementById('open-modal-btn').onclick = setUrlInModal;
document.getElementById('download-ics').onclick = downloadIcs;
document.getElementById('add-to-system').onclick = addToSystemCalendar;
document.getElementById('share-url').innerText = `${window.location.protocol}//${window.location.hostname}/schedule/view?section_ids=${window.cart._courses.join(',')}`;
};
......@@ -13,3 +13,7 @@
.section-item.selected:hover {
background-color: red;
}
#share-header {
margin-top: 16px;
}
......@@ -11,4 +11,9 @@ class SchedulesController < ApplicationController
@all = valid_ids.map { |id| CourseSection.find_by_id id }
@events = generate_fullcalender_events(valid_ids)
end
def view
@all = params[:section_ids].split(',').map { |id| CourseSection.find_by_id id.to_s }
@events = generate_fullcalender_events(params[:section_ids].split(','))
end
end
......@@ -23,7 +23,9 @@ class SessionsController < ApplicationController
def add_bulk
crns = params[:crns].split(',')
crns.each { |crn|
section_id = CourseSection.find_by_crn(crn).id.to_s
s = CourseSection.find_by_crn(crn)
next if s.nil?
section_id = s.id.to_s
@cart << section_id unless @cart.include?(section_id)
}
cookies[:cart] = @cart.to_json
......
......@@ -45,7 +45,7 @@ module SearchHelper
CourseReplacementHelper.replace!(query_string)
base_query = Course.select("courses.*, count(course_sections.id) AS section_count")
.left_outer_joins(:course_sections)
.having("section_count > 0")
.having("count(course_sections.id) > 0")
.where("courses.semester_id = ?", query_data.semester)
.group("courses.id")
......@@ -53,6 +53,7 @@ module SearchHelper
query_string.scan(/(?<= |^)([a-zA-Z]{2,4})(?=$| )/).each do |a|
s = a[0]
next unless get_count(Course.from_subject(base_query, s)).positive?
# next unless Course.from_subject(base_query, s).count.positive?
subj = s
base_query = Course.from_subject(base_query, subj)
query_string.remove!(s)
......@@ -61,6 +62,7 @@ module SearchHelper
query_string.scan(/(?<= |^)(\d{3})(?=$| )/).each do |a|
s = a[0]
next unless !subj.nil? && get_count(Course.from_course_number(base_query, s)).positive?
# next unless !subj.nil? && Course.from_course_number(base_query, s).count.positive?
base_query = Course.from_course_number(base_query, s)
return base_query.all
end
......@@ -90,7 +92,7 @@ module SearchHelper
def self.get_count(base_query)
# I think I finally hit a limit of active record
ActiveRecord::Base.connection.execute("SELECT COUNT(*) AS count FROM (#{base_query.to_sql})")[0]["count"]
ActiveRecord::Base.connection.execute("SELECT COUNT(*) AS count FROM (#{base_query.to_sql}) as q")[0]["count"]
end
def to_s
......
......@@ -19,15 +19,17 @@ class CourseSection < ApplicationRecord
# Select all course sections that have an instructor that matches the given name
def self.with_instructor(name: "")
joins(:instructor).where("instructors.name LIKE ?", "%#{name}%").select('course_sections.*, instructors.name as instructor_name')
joins(:instructor)
.where("instructors.name LIKE ?", "%#{name}%")
.select('course_sections.*, instructors.name as instructor_name')
end
def self.from_crn(base_query, crn)
base_query.where("course_sections.crn = ?", crn)
base_query.where(crn: crn)
end
def self.from_course_id(base_query, course_id)
base_query.where("course_sections.course_id = ?", course_id)
base_query.where(course_id: course_id)
end
# Select all revelevant course sections given the provided filters
......
......@@ -2,6 +2,6 @@ class Instructor < ApplicationRecord
has_many :course_sections
def self.from_name(base_query, name)
base_query.where("instructors.name LIKE ?", "%#{name}%")
base_query.where("upper(instructors.name) LIKE ?", "%#{name.upcase}%")
end
end
......@@ -34,6 +34,10 @@
</div>
</form>
<h3 id="share-header">Share</h3>
Want to share your schedule with your friends? Send them this link:<br/>
<blockquote ><code id="share-url"></code></blockquote>
<template id="events" data-events="<%= @events.to_json %>"></template>
<hr />
......
<%= javascript_include_tag 'schedules_view' %>
<%= stylesheet_link_tag 'schedules' %>
<%= javascript_include_tag 'moment.min' %>
<%= javascript_include_tag 'FileSaver' %>
<%= javascript_include_tag 'fullcalendar.min'%>
<%= stylesheet_link_tag 'fullcalendar.min' %>
<button id="open-modal-btn" type="button" class="btn btn-primary" data-toggle="modal" data-target="#exportModal">
Export Schedule
</button>
<div id="calendar"></div>
<template id="events" data-events="<%= @events.to_json %>"></template>
<hr />
<h2>Selected Courses</h2>
<%= render partial: 'shared/section', collection: @all, locals: { editable: false } %>
<!-- Export Modal -->
<div class="modal fade" id="exportModal" tabindex="-1" role="dialog" aria-labelledby="exportModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exportModalLabel">Your calendar has been generated!</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<h5>Apple Calendar</h5>
To add your schedule to Apple Calendar, click the "Add to calendar" button below. If you are on a device
running macOS or iOS, this will open a dialogue which will walk you through adding the calendar.
<hr />
<h5>Google Calendar</h5>
<strong>On desktop:</strong>
<br />
Open your <a href="https://calendar.google.com/">Google Calendar</a>. Click the "Settings" button in the top
right, and then click the Settings tab. In the menu on the left, click "Add calendar" and "From URL". Now,
paste the following link inside the text box: <br />
<code id="calendar-link"></code>
<br />
<strong>On mobile (Android only):</strong>
<br />
Click the "Download calendar file" button. This will download the calendar file which you may then open and
add to your calendar.
<hr />
<h5>.ics file</h5>
To download a .ics file containing your schedule, click the "Download calendar file" button below.
</div>
<div class="modal-footer flex">
<button id="download-ics" type="button" class="btn btn-secondary">Download calendar file</button>
<button id="add-to-system" type="button" class="btn btn-primary">Add to system calendar</button>
</div>
</div>
</div>
</div>
......@@ -43,9 +43,9 @@
<!-- List of Course Sections -->
<div class="list-group list-group-flush sections" style="display: <%= expanded ? "flex" : "none" %>">
<% if defined?(@instructor) %>
<%= render partial: 'shared/section', collection: course.course_sections.where(instructor: @instructor), locals: { course: course } %>
<%= render partial: 'shared/section', collection: course.course_sections.where(instructor: @instructor).order(:name), locals: { course: course } %>
<% else %>
<%= render partial: 'shared/section', collection: course.course_sections, locals: { course: course } %>
<%= render partial: 'shared/section', collection: course.course_sections.order(:name), locals: { course: course } %>
<% end %>
</div>
</div>
......
<% unless defined?(editable) %>
<% editable = true %>
<% end %>
<li id="section-<%= section.id %>"
class="list-group-item section-item"
data-crn="<%= section.crn %>"
......@@ -7,13 +11,20 @@
>
<p><b class="subj"><%= "#{section.name}" %></b>: <%= section.title %></p>
<% if in_cart? section.id %>
<span class="float-right text-center add-remove-btn"><i id="icon" class="fas fa-minus"></i><br/><span class="text">Remove</span></span>
<% else %>
<span class="float-right text-center add-remove-btn"><i id="icon" class="fas fa-plus"></i><br/><span class="text">Add</span></span>
<% if editable %>
<% if in_cart? section.id %>
<span class="float-right text-center add-remove-btn"><i id="icon" class="fas fa-minus"></i><br/><span class="text">Remove</span></span>
<% else %>
<span class="float-right text-center add-remove-btn"><i id="icon" class="fas fa-plus"></i><br/><span class="text">Add</span></span>
<% end %>
<% end %>
<i class="fas fa-chalkboard-teacher"></i> <%= link_to section.instructor.name, section.instructor %> <br/>
<i class="fas fa-chalkboard-teacher"></i>
<% if section.instructor.name == "TBA" %>
TBA
<% else %>
<%= link_to section.instructor.name, section.instructor %><% end %> <br/>
<i class="fas fa-map-marker-alt"></i> <%= section.location %> <br/>
<i class="fas fa-clock"></i> <%= "#{section.days}, #{section.start_time}-#{section.end_time}" %> <br/>
</li>
......@@ -21,5 +21,12 @@ test:
database: db/test.sqlite3
production:
<<: *default
database: db/production.sqlite3
adapter: postgresql
encoding: unicode
database: <%= ENV.fetch("DB_DATABASE") { "" } %>
username: <%= ENV.fetch("DB_USERNAME") { "" } %>
password: <%= ENV.fetch("DB_PASSWORD") { "" } %>
host: <%= ENV.fetch("DB_HOST") { "localhost" } %>
port: <%= ENV.fetch("DB_PORT") { 5432 } %>
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
......@@ -16,6 +16,7 @@ Rails.application.config.assets.precompile += %w(
search.scss
schedules.js
schedules_view.js
schedules.scss
cart.js
......
......@@ -9,6 +9,7 @@ Rails.application.routes.draw do
resources :courses, only: [:show]
resources :instructors, only: [:index, :show]
get 'schedule', to: 'schedules#show', as: 'schedule'
get 'schedule/view', to: 'schedules#view', as: 'view_schedule'
scope :api, module: 'api' do # Register /api routes
resources :courses, only: [:index, :show], as: 'api_courses'
......
......@@ -12,9 +12,12 @@
ActiveRecord::Schema.define(version: 20180927140017) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "closures", force: :cascade do |t|
t.date "date"
t.integer "semester_id"
t.bigint "semester_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["semester_id"], name: "index_closures_on_semester_id"
......@@ -35,10 +38,10 @@ ActiveRecord::Schema.define(version: 20180927140017) do
t.string "campus"
t.string "notes"
t.integer "size_limit"
t.integer "course_id"
t.bigint "course_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "instructor_id"
t.bigint "instructor_id"
t.index ["course_id"], name: "index_course_sections_on_course_id"
t.index ["instructor_id"], name: "index_course_sections_on_instructor_id"
end
......@@ -46,7 +49,7 @@ ActiveRecord::Schema.define(version: 20180927140017) do
create_table "courses", force: :cascade do |t|
t.string "subject"
t.string "course_number"
t.integer "semester_id"
t.bigint "semester_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "description"
......@@ -69,4 +72,8 @@ ActiveRecord::Schema.define(version: 20180927140017) do
t.datetime "updated_at", null: false
end
add_foreign_key "closures", "semesters"
add_foreign_key "course_sections", "courses"
add_foreign_key "course_sections", "instructors"
add_foreign_key "courses", "semesters"
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment