Skip to content

Commit

Permalink
Merge pull request #42 from marcoroth/ical
Browse files Browse the repository at this point in the history
Add ics integration for events and add events show page
  • Loading branch information
crespire authored Sep 4, 2024
2 parents fc269ff + e426f6d commit 3e0f3b8
Show file tree
Hide file tree
Showing 27 changed files with 339 additions and 40 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Tests

on: [push, pull_request]

jobs:
tests:
runs-on: ubuntu-latest
name: Tests

services:
postgres:
image: postgres:15.0-alpine

env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres

ports:
- 5432:5432

options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

steps:
- uses: actions/checkout@v4

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true

- name: Setup Test Database
run: bundle exec rails db:setup
env:
RAILS_ENV: test

- name: Run tests
run: bundle exec rails test
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ gem 'bootsnap', require: false
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"

# Internet calendaring, Ruby style
gem 'icalendar', '~> 2.10'

group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem 'debug', platforms: %i[mri windows]
Expand Down
7 changes: 7 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ GEM
honeybadger (5.11.2)
i18n (1.14.5)
concurrent-ruby (~> 1.0)
icalendar (2.10.2)
ice_cube (~> 0.16)
ice_cube (0.17.0)
io-console (0.7.2)
irb (1.13.1)
rdoc (>= 4.0.0)
Expand Down Expand Up @@ -131,6 +134,8 @@ GEM
nio4r (2.7.3)
nokogiri (1.16.6-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.6-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.6-x86_64-linux)
racc (~> 1.4)
pg (1.5.6)
Expand Down Expand Up @@ -216,13 +221,15 @@ GEM

PLATFORMS
arm64-darwin-23
x86_64-darwin-22
x86_64-linux

DEPENDENCIES
bootsnap
cssbundling-rails
debug
honeybadger (~> 5.6)
icalendar (~> 2.10)
jbuilder
jsbundling-rails
pg
Expand Down
6 changes: 4 additions & 2 deletions app/controllers/admin/base_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module Admin
class BaseController < ApplicationController
http_basic_authenticate_with name: Rails.application.credentials.auth.user,
password: Rails.application.credentials.auth.password
http_basic_authenticate_with(
name: Rails.env.test? ? "admin" : Rails.application.credentials.auth.user,
password: Rails.env.test? ? "admin" : Rails.application.credentials.auth.password
)
end
end
15 changes: 12 additions & 3 deletions app/controllers/events_controller.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
class EventsController < ApplicationController
# GET /events or /events.json
# GET /events
def index
@events = Event.upcoming
end

# GET /events/all or /events/all.ics
def all
@events = Event.all
render :index
@events = Event.published

respond_to do |format|
format.html { render :index }
format.ics { render plain: Calendar.new(@events.order(start_at: :asc)).to_ical }
end
end

def show
@event = Event.find_by(slug: params[:slug])
end

def past
Expand Down
41 changes: 41 additions & 0 deletions app/javascript/controllers/clipboard_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ["button", "source"]

static values = {
successContent: String,
successDuration: {
type: Number,
default: 2000,
}
}

connect() {
if (!this.hasButtonTarget) return

this.originalContent = this.buttonTarget.innerHTML
}

copy(event) {
event.preventDefault()

const text = this.sourceTarget.innerHTML || this.sourceTarget.value

navigator.clipboard.writeText(text).then(() => this.copied())
}

copied() {
if (!this.hasButtonTarget) return

if (this.timeout) {
clearTimeout(this.timeout)
}

this.buttonTarget.innerHTML = this.successContentValue

this.timeout = setTimeout(() => {
this.buttonTarget.innerHTML = this.originalContent
}, this.successDurationValue)
}
}
7 changes: 0 additions & 7 deletions app/javascript/controllers/hello_controller.js

This file was deleted.

4 changes: 2 additions & 2 deletions app/javascript/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@

import { application } from "./application"

import HelloController from "./hello_controller"
application.register("hello", HelloController)
import ClipboardController from "./clipboard_controller"
application.register("clipboard", ClipboardController)
80 changes: 80 additions & 0 deletions app/models/calendar.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
require "icalendar"

class Calendar
include Rails.application.routes.url_helpers

def initialize(events)
@events = events
end

def publish
calendar = Icalendar::Calendar.new

calendar.timezone do |timezone|
timezone.tzid = timezone_identifier
end

@events.each do |event|
calendar.event do |e|
e.dtstart = ical_time(event.start_at)
e.dtend = ical_time(event.start_at + 2.hours)
e.summary = "Toronto Ruby - #{event.name}"
e.location = event.city
e.url = event.rsvp_link || event_url(event)

sponsor = event.sponsor ? <<~SPONSOR : nil
Sponsor:
#{event.sponsor} (#{event.sponsor_link})
SPONSOR

rsvp_link = event.rsvp_link ? <<~RSVP : nil
RSVP:
#{event.rsvp_link}
RSVP

e.description = <<~DESCRIPTION
Toronto Ruby - #{event.name}
Location:
#{event.location}
Presenter:
#{event.presenter}
Presentation:
#{event.presentation}
Event-Site:
#{event_url(event)}
#{rsvp_link}
#{sponsor}
DESCRIPTION
end
end

calendar.publish
end

def to_ical
publish.to_ical
end

private

def timezone_identifier
"America/Toronto"
end

def timezone
@timezone ||= TZInfo::Timezone.get(timezone_identifier)
end

def ical_timezone
timezone.ical_timezone(timezone.now)
end

def ical_time(datetime)
Icalendar::Values::DateTime.new(datetime, tzid: timezone_identifier)
end
end
4 changes: 4 additions & 0 deletions app/models/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ def start_time
def self.statuses_for_select
statuses.map { |k, _v| [k.titleize, k] }
end

def to_param
slug
end
end
3 changes: 1 addition & 2 deletions app/views/events/_event.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<% future = Time.zone.now < event.start_at %>
<div class="md:flex text-left">
<div class="w-full md:w-1/2 my-8 pr-1">
<h2 class="mb-2"><%= event.name %></h2>
<h2 class="mb-2"><%= link_to event.name, event %></h2>
<div class="flex flex-col gap-3">
<p class="mb-2"><%= event.start_time %></p>
<%= simple_format(event.location) %>
Expand Down Expand Up @@ -50,4 +50,3 @@
</div>
</div>
</div>

18 changes: 18 additions & 0 deletions app/views/events/_layout.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<main class="text-center p-3 max-w-screen-xl md:max-w-screen-md">
<div class="flex flex-col gap-6">
<div class="text-center">
<h1>
Monthly meetups for Toronto's Ruby community
</h1>

<p>We're a group of Ruby developers who meet up to talk about programming, the industry, and life. We're working to strengthen the local Ruby community and provide a positive space for everyone to connect.</p>
</div>
<div class="text-center flex flex-col gap-3">
<%= yield(:event_content) %>
</div>
<div class="mx-auto w-4/5 md:w-2/3">
<%= render 'newsletter_form' %>
</div>
<%= render 'supporting_companies' %>
</div>
</main>
28 changes: 7 additions & 21 deletions app/views/events/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,26 +1,11 @@
<main class="text-center p-3 max-w-screen-xl md:max-w-screen-md">
<div class="flex flex-col gap-6">
<div class="text-center">
<h1>
Monthly meetups for Toronto's Ruby community
</h1>
<%= content_for :event_content do %>
<%# For an upcoming event, make a "past_event" and use the partial here %>
<%# Otherwise, use the placeholder partial %>
<p>We're a group of Ruby developers who meet up to talk about programming, the industry, and life. We're working to strengthen the local Ruby community and provide a positive space for everyone to connect.</p>
</div>
<div class="text-center flex flex-col gap-3">
<%# For an upcoming event, make a "past_event" and use the partial here %>
<%# Otherwise, use the placeholder partial %>
<%= render partial: "event", collection: @events %>

<%= render partial: "event", collection: @events %>

<h2 class="hidden only:block">We're planning our next outing, stay tuned!</h2>
</div>
<div class="mx-auto w-4/5 md:w-2/3">
<%= render 'newsletter_form' %>
</div>
<%= render 'supporting_companies' %>
</div>
</main>
<h2 class="hidden only:block">We're planning our next outing, stay tuned!</h2>
<% end %>
<% content_for :page_title do %>
Home
Expand All @@ -30,3 +15,4 @@ Home
Toronto Ruby is about building a community for Ruby users in the GTA and providing a positive space for everyone to connect, learn and grow. We're looking forward to seeing you!
<% end %>
<%= render partial: "events/layout" %>
5 changes: 5 additions & 0 deletions app/views/events/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<%= content_for :event_content do %>
<%= render partial: "event", locals: { event: @event } %>
<% end %>
<%= render partial: "events/layout" %>
1 change: 1 addition & 0 deletions app/views/layouts/common/_navigation.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
Propose a talk<svg class="stroke-ruby w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" /></svg>
<% end %>
<%= link_to 'About', about_path %>
<%= link_to 'Calendar', calendar_path %>
<%= link_to 'https://www.youtube.com/@TorontoRuby', class: 'flex gap-1 justify-center items-center' do %>
Youtube<svg class="stroke-ruby w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" /></svg>
<% end %>
Expand Down
32 changes: 32 additions & 0 deletions app/views/static/calendar.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<main class="text-center p-3 max-w-screen-xl md:max-w-screen-md">
<div class="flex flex-col gap-6">
<div class="text-center">
<h1 class="mb-6">
Icalendar Integration
</h1>

<p class="mb-4">
Add the Toronto Ruby events as a calendar subscription to your own calendar.
</p>

<p class="mb-4">
Copy the following URL and import the calendar:
</p>

<div data-controller="clipboard" data-action="clipboard#copy" data-clipboard-success-content-value="" class="mt-1 inline-flex rounded-md">
<input data-action="clipboard#copy" type="text" value="<%= all_events_url(:ics) %>" data-clipboard-target="source" readonly class="py-3 px-6 flex-1 text-sm rounded-l-md border border-gray-300 bg-gray-50 w-72">
<button type="button" data-action="clipboard#copy" data-clipboard-target="button" class="inline-flex items-center px-3 rounded-r-md border border-l-0 border-gray-300 bg-gray-50 text-gray-500 text-sm">
💾
</button>
</div>
</div>
</div>
</main>

<% content_for :page_title do %>
Calendar
<% end %>
<% content_for :page_description do %>
Add the Toronto Ruby events as a calendar subscription to your own calendar.
<% end %>
5 changes: 5 additions & 0 deletions config/database.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ development:
test:
<<: *default
database: site_v2_test
username: postgres
password: postgres
port: 5432
host: localhost
encoding: utf8

# As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
Expand Down
Loading

0 comments on commit 3e0f3b8

Please sign in to comment.