Compare commits

..

24 Commits

Author SHA1 Message Date
Petko Bordjukov deebcbc95a New dev-friendly schedule endpoint 2024-10-03 23:25:01 +03:00
Tocho Tochev 5660c3ef53 Update talk confirmation template 2024-10-01 15:24:50 +03:00
Petko Bordjukov 71e8ab74da Limit access to archived conferences 2024-10-01 14:59:48 +03:00
Petko Bordjukov 89dd1890fd Add freshness headers to API 2024-10-01 01:36:19 +03:00
Petko Bordjukov 1018b93b11 Introduce thruster 2024-10-01 00:55:19 +03:00
Tocho Tochev 8cd4a4856d Remove the help text 2024-09-25 02:30:08 +03:00
Tocho Tochev 0cffe052c8 Ask volunteers to agree to our ToS 2024-09-25 02:30:08 +03:00
Tocho Tochev 6af2d09896 Fix speaker picture and add name 2024-09-23 13:24:42 +03:00
Tocho Tochev ef5935bd85 Fix race condition on emailing for new proposition 2024-09-22 23:02:25 +03:00
Tocho Tochev 10e68f3453 Use ruby 3.3.5 in all places 2024-09-22 22:44:30 +03:00
Petko Bordjukov 5bbe601110 Enable caching of halfnarp talks 2024-09-13 23:42:55 +03:00
Petko Bordjukov 89438b474b Limit data returned by halfnarp-friendly endpoint 2024-09-13 23:33:55 +03:00
Petko Bordjukov 7a64633ac0 Revert "Speed up json generation"
This reverts commit 6171e484cc.
2024-09-13 23:09:16 +03:00
Petko Bordjukov 6171e484cc Speed up json generation 2024-09-13 22:58:10 +03:00
Petko Bordjukov 2bacd84654 Assume SSL 2024-09-13 22:22:43 +03:00
Petko Bordjukov fc08089796 Introduce volunteer deletion 2024-09-08 18:27:47 +03:00
Petko Bordjukov 93611a3bd9 Configure Rubocop to work with StandardRB 2024-07-03 23:17:22 +03:00
Tocho Tochev 459be53b5c Notify with anonymised email on new volunteer (#48)
People enjoy the instant gratification of a random alert.

This brings back email notifications to organizers on volunteer sign-up so that relevant people can react as soon as possible.

The email now will contain anonymous data (team name and t-shirt size) and a link for curious people to view the profile.

I have considered adding another email address for such notifications, but it seems unnecessarily complicated.

Proof of work:

![image](/attachments/dabd0375-17ac-43c8-9698-678e768aa111)

![image](/attachments/fed16195-e668-47c7-9647-5fe069f1ed2d)

Reviewed-on: #48
Co-authored-by: Tocho Tochev <tocho@tochev.net>
Co-committed-by: Tocho Tochev <tocho@tochev.net>
2024-06-01 12:32:39 +03:00
Tocho Tochev bcac28d4ff Fix volunteer counts shown for a team (#46)
Before this change the count of volunteers in a team was not in sync with the filters applied.

![image](/attachments/0a3a0c5c-f3c2-4ef4-89d7-0bcf1fb9302d)

After the change:
![image](/attachments/b88719f6-fadf-4f07-b4d4-985533b0ea2f)

BTW I feel that we will end up refactoring the relation at some point in the future.

Reviewed-on: #46
Co-authored-by: Tocho Tochev <tocho@tochev.net>
Co-committed-by: Tocho Tochev <tocho@tochev.net>
2024-06-01 12:32:12 +03:00
Tocho Tochev 5ff505d246 Add indication for unverified volunteers (#45)
We want to have some indication for that some volunteers haven't confirmed their email.

Perhaps "unverified" is bad terminology, but until we have proper "verification" it will suffice.

(Ideally there would also be a filter, but my rails is way too rusty...)

Reviewed-on: #45
Co-authored-by: Tocho Tochev <tocho@tochev.net>
Co-committed-by: Tocho Tochev <tocho@tochev.net>
2024-06-01 12:31:38 +03:00
Tocho Tochev 87df897fe9 Remove stray docker-ignore (#43)
I don't think the `.docker-ignore` does anything when there is a `.dockerignore` so let's remove it.

I'm open to amending the PR.

Reviewed-on: #43
Co-authored-by: Tocho Tochev <tocho@tochev.net>
Co-committed-by: Tocho Tochev <tocho@tochev.net>
2024-06-01 12:30:57 +03:00
Tocho Tochev d44738bf58 Add localhost as name to initfest domains (#44)
Before this change if someone is using `localhost` for development instead of `127.0.0.1`, he would get a missing template error.

Reviewed-on: #44
Co-authored-by: Tocho Tochev <tocho@tochev.net>
Co-committed-by: Tocho Tochev <tocho@tochev.net>
2024-06-01 12:30:16 +03:00
Tocho Tochev 486a763277 Add docker-compose (#47)
This is a docker-compose based on production config, without mounting of the local folders, which I admit is a strange setup, but this was the simplest for me to get the local development going.

I'm perfectly ok with not merging this PR.

Reviewed-on: #47
Co-authored-by: Tocho Tochev <tocho@tochev.net>
Co-committed-by: Tocho Tochev <tocho@tochev.net>
2024-06-01 12:29:46 +03:00
Tocho Tochev cd8c3bbcc7 Add devise BG locale (#49)
I will raise the changes to the upstream project after review https://github.com/tochev/devise-i18n/pull/1/files

Co-authored-by: Albert Stefanov <aastefanov@outlook.com>
Reviewed-on: #49
Co-authored-by: Tocho Tochev <tocho@tochev.net>
Co-committed-by: Tocho Tochev <tocho@tochev.net>
2024-06-01 12:29:13 +03:00
51 changed files with 2346 additions and 52 deletions

View File

@ -1,4 +0,0 @@
.git
*.tar.gz
*.sql
node_modules

View File

@ -35,3 +35,9 @@
/app/assets/builds/* /app/assets/builds/*
!/app/assets/builds/.keep !/app/assets/builds/.keep
/public/assets /public/assets
# Archives
*.tar.gz
# SQL
*.sql

7
.rubocop.yml Normal file
View File

@ -0,0 +1,7 @@
require: standard
inherit_gem:
standard: config/base.yml
AllCops:
DisabledByDefault: true

View File

@ -1 +1 @@
3.3.0 3.3.5

View File

@ -2,7 +2,7 @@ language: ruby
dist: xenial dist: xenial
cache: bundler cache: bundler
rvm: rvm:
- 2.6 - 3.3
script: script:
- RAILS_ENV=test bundle exec rake --trace bootstrap spec - RAILS_ENV=test bundle exec rake --trace bootstrap spec
addons: addons:

View File

@ -1,7 +1,7 @@
# syntax = docker/dockerfile:1 # syntax = docker/dockerfile:1
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile # Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.3.0 ARG RUBY_VERSION=3.3.5
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base
# Rails app lives here # Rails app lives here
@ -58,5 +58,5 @@ USER rails:rails
ENTRYPOINT ["/rails/bin/docker-entrypoint"] ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Start the server by default, this can be overwritten at runtime # Start the server by default, this can be overwritten at runtime
EXPOSE 3000 EXPOSE 80
CMD ["./bin/rails", "server", "--early-hints"] CMD ["./bin/thrust", "./bin/rails", "server", "--early-hints"]

View File

@ -88,3 +88,5 @@ group :test do
gem "database_cleaner" gem "database_cleaner"
gem "factory_bot_rails" gem "factory_bot_rails"
end end
gem "thruster", "~> 0.1.8"

View File

@ -491,6 +491,7 @@ GEM
terminal-table (3.0.2) terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3) unicode-display_width (>= 1.1.1, < 3)
thor (1.3.0) thor (1.3.0)
thruster (0.1.8)
tilt (2.3.0) tilt (2.3.0)
timeout (0.4.1) timeout (0.4.1)
tzinfo (2.0.6) tzinfo (2.0.6)
@ -568,6 +569,7 @@ DEPENDENCIES
sprockets sprockets
sqlite3 sqlite3
standard standard
thruster (~> 0.1.8)
uglifier uglifier
yaml_db yaml_db

View File

@ -1,11 +1,31 @@
Clarion # Clarion
=======
A CfP automation system for OpenFest. A CfP automation system for OpenFest.
Installation ## Installation
------------
1. `git clone https://github.com/ignisf/clarion.git` ### For local development
2. Run `bundle install; bin/rake bootstrap`
3. You can now run the rails server with `bin/rails s` 1. `git clone https://git.openfest.org/Site/clarion/`
2. Run `rvm install "ruby-$(cat .ruby-version)"; rvm install "ruby-$(cat .ruby-version)"`
3. Start up postgresql
4. Run `bundle install; bin/rake bootstrap`
5. You can now run the rails server with `bin/rails s`
### For production
`docker build -t clarion:latest -f Dockerfile .`
Note that the docker image contains a default user (for credentials see `db/seeds.rb`).
### docker-compose
`docker-compose up` will bring everything up on `http://127.0.0.1:3000` with production configuration.
## Initial Usage
1. Go to `http://127.0.0.1:3000/management/`
2. Login (for initial creds see `db/seeds.rb`)
3. Change the credentials
4. Create a conference.
- NB: When creating a conference make sure to use the same `domain` as the one you are currently using, otherwise nothing will be shown.

View File

@ -3,5 +3,6 @@ class Api::ConferencesController < Api::ApplicationController
def index def index
@conferences = Conference.all @conferences = Conference.all
fresh_when @conferences
end end
end end

View File

@ -5,5 +5,6 @@ class Api::EventTypesController < Api::ApplicationController
def index def index
@event_types = current_conference.event_types.includes(:translations) @event_types = current_conference.event_types.includes(:translations)
fresh_when @event_types
end end
end end

View File

@ -9,6 +9,5 @@ class Api::EventsController < Api::ApplicationController
def halfnarp_friendly def halfnarp_friendly
@events = current_conference.events.joins(:proposition).includes(:track, :event_type).where.not(propositions: {status: :rejected}) @events = current_conference.events.joins(:proposition).includes(:track, :event_type).where.not(propositions: {status: :rejected})
render json: @events, include: [:track, :event_type]
end end
end end

View File

@ -5,5 +5,6 @@ class Api::HallsController < Api::ApplicationController
def index def index
@halls = current_conference.halls @halls = current_conference.halls
fresh_when @halls
end end
end end

View File

@ -0,0 +1,9 @@
class Api::SchedulesController < Api::ApplicationController
include ::CurrentConferenceAssigning
include ::PublicApiExposing
before_action :require_current_conference!
def show
@halls = Conference.last.halls.includes(:translations, slots: {approved_event: [:participants_with_personal_profiles, :proposition]})
end
end

View File

@ -5,5 +5,7 @@ class Api::SlotsController < Api::ApplicationController
def index def index
@slots = current_conference.slots @slots = current_conference.slots
fresh_when @slots
end end
end end

View File

@ -5,5 +5,6 @@ class Api::SpeakersController < Api::ApplicationController
def index def index
@speakers = PersonalProfile.joins(user: {participations: {event: :proposition}}).where(events: {id: current_conference.approved_events.pluck(:id)}, conference: current_conference).distinct @speakers = PersonalProfile.joins(user: {participations: {event: :proposition}}).where(events: {id: current_conference.approved_events.pluck(:id)}, conference: current_conference).distinct
fresh_when @speakers
end end
end end

View File

@ -5,5 +5,6 @@ class Api::TracksController < Api::ApplicationController
def index def index
@tracks = current_conference.tracks.includes(:translations) @tracks = current_conference.tracks.includes(:translations)
fresh_when @tracks
end end
end end

View File

@ -33,7 +33,8 @@ class ApplicationController < ActionController::Base
# TODO: make this get the domain from the database # TODO: make this get the domain from the database
prepend_view_path "lib/initfest/views" if request.host =~ /openfest/ prepend_view_path "lib/initfest/views" if request.host =~ /openfest/
prepend_view_path "lib/initfest/views" if request.host =~ /example/ prepend_view_path "lib/initfest/views" if request.host =~ /example/
prepend_view_path "lib/initfest/views" if request.host =~ /127\.0\.0/ prepend_view_path "lib/initfest/views" if request.host =~ /^127\.0\.0/
prepend_view_path "lib/initfest/views" if request.host =~ /^localhost$/
end end
protected protected

View File

@ -8,7 +8,11 @@ module Management
private private
def authorize_user! def authorize_user!
head :forbidden unless current_user.admin? if params[:conference_id] && params[:conference_id].to_i < Conference.last.id
head :forbidden unless current_user.admin? && current_user.owner?
else
head :forbidden unless current_user.admin?
end
end end
end end
end end

View File

@ -15,6 +15,12 @@ module Management
@volunteer = current_conference.volunteers.find(params[:id]) @volunteer = current_conference.volunteers.find(params[:id])
end end
def destroy
@volunteer = current_conference.volunteers.find(params[:id])
@volunteer.destroy!
redirect_to management_conference_volunteers_path(conference_id: current_conference.id)
end
def update def update
@volunteer = current_conference.volunteers.find(params[:id]) @volunteer = current_conference.volunteers.find(params[:id])
@ -35,7 +41,7 @@ module Management
params.require(:volunteer).permit(:name, :picture, :email, :phone, params.require(:volunteer).permit(:name, :picture, :email, :phone,
:tshirt_size, :tshirt_cut, :tshirt_size, :tshirt_cut,
:food_preferences, :previous_experience, :food_preferences, :previous_experience,
:notes, :language, :notes, :language, :terms_accepted,
:volunteer_team_id, :volunteer_team_id,
additional_volunteer_team_ids: []) additional_volunteer_team_ids: [])
end end

View File

@ -39,7 +39,7 @@ module Public
params.require(:volunteer).permit( params.require(:volunteer).permit(
:name, :picture, :email, :phone, :tshirt_size, :tshirt_cut, :name, :picture, :email, :phone, :tshirt_size, :tshirt_cut,
:food_preferences, :previous_experience, :notes, :language, :food_preferences, :previous_experience, :notes, :language,
:volunteer_team_id :terms_accepted, :volunteer_team_id,
) )
end end
end end

View File

@ -4,7 +4,7 @@ class VolunteerMailer < ActionMailer::Base
mail( mail(
to: @volunteer.conference.email, to: @volunteer.conference.email,
subject: "Нов доброволец за #{@volunteer.conference.title}" subject: "Нов доброволец за #{@volunteer.conference.title} - #{@volunteer.volunteer_team.name}"
) )
end end

View File

@ -20,6 +20,7 @@ class Event < ActiveRecord::Base
scope :ranked, -> { where.not(ranked: nil).where.not(votes: nil) } scope :ranked, -> { where.not(ranked: nil).where.not(votes: nil) }
scope :approved, -> { where(propositions: {status: Proposition.statuses[:approved]})} scope :approved, -> { where(propositions: {status: Proposition.statuses[:approved]})}
scope :approved_joined, -> { joins(:proposition).merge(Proposition.approved) }
validates :conference, presence: true validates :conference, presence: true
validates :title, presence: true, length: {maximum: 65} validates :title, presence: true, length: {maximum: 65}

View File

@ -4,7 +4,7 @@ class Proposition < ActiveRecord::Base
enum status: [:undecided, :approved, :rejected, :backup] enum status: [:undecided, :approved, :rejected, :backup]
delegate :proposable_title, :proposable_type, :proposable_description, to: :proposable delegate :proposable_title, :proposable_type, :proposable_description, to: :proposable
after_create :send_creation_notification after_commit :send_creation_notification, on: [:create]
before_destroy :send_withdrawal_notification before_destroy :send_withdrawal_notification
def confirm! def confirm!

View File

@ -1,4 +1,5 @@
class Slot < ActiveRecord::Base class Slot < ActiveRecord::Base
belongs_to :hall belongs_to :hall
belongs_to :event, required: false belongs_to :event, required: false
belongs_to :approved_event, -> { joins(:proposition).approved_joined }, class_name: 'Event', foreign_key: 'event_id'
end end

View File

@ -12,6 +12,7 @@ class Volunteer < ActiveRecord::Base
validates :email, format: {with: /\A[^@]+@[^@]+\z/}, presence: true, uniqueness: {scope: :conference_id} validates :email, format: {with: /\A[^@]+@[^@]+\z/}, presence: true, uniqueness: {scope: :conference_id}
validates :phone, presence: true, format: {with: /\A[+\- \(\)0-9]+\z/} validates :phone, presence: true, format: {with: /\A[+\- \(\)0-9]+\z/}
validates :volunteer_team, presence: true validates :volunteer_team, presence: true
validates :terms_accepted, acceptance: true
validate :volunteer_teams_belong_to_conference validate :volunteer_teams_belong_to_conference
phony_normalize :phone, default_country_code: "BG" phony_normalize :phone, default_country_code: "BG"
@ -24,6 +25,7 @@ class Volunteer < ActiveRecord::Base
before_create :assign_unique_id before_create :assign_unique_id
before_create :assign_confirmation_token before_create :assign_confirmation_token
after_commit :send_email_confirmation_to_volunteer, on: [:create] after_commit :send_email_confirmation_to_volunteer, on: [:create]
after_commit :send_email_to_organisers, on: [:create] # technically the volunteer's email is not confirmed yet
def send_notification_to_volunteer def send_notification_to_volunteer
VolunteerMailer.volunteer_notification(self).deliver_later VolunteerMailer.volunteer_notification(self).deliver_later
@ -47,6 +49,10 @@ class Volunteer < ActiveRecord::Base
VolunteerMailer.volunteer_email_confirmation(self).deliver_later VolunteerMailer.volunteer_email_confirmation(self).deliver_later
end end
def send_email_to_organisers
VolunteerMailer.team_notification(self).deliver_later
end
def volunteer_teams_belong_to_conference def volunteer_teams_belong_to_conference
conference_volunteer_teams = conference.volunteer_teams conference_volunteer_teams = conference.volunteer_teams
unless additional_volunteer_teams.all? { |team| conference_volunteer_teams.include? team } unless additional_volunteer_teams.all? { |team| conference_volunteer_teams.include? team }

View File

@ -0,0 +1,13 @@
json.array! @events, cached: ->(event) { [event, event.track, event.event_type] } do |event|
json.id event.id
json.title event.title
json.abstract event.abstract
json.track_id event.track_id
json.track do
json.name event.track.name
end
json.event_type do
json.name event.event_type.name
end
end

View File

@ -0,0 +1,22 @@
@halls.each do |hall|
json.set! hall.name do
json.days do
hall.slots.to_a.sort_by(&:starts_at).group_by { |slot| slot.starts_at.to_date }.each do |day, slots|
json.set! day do
json.array! slots do |slot|
next unless slot.approved_event
json.starts_at slot.starts_at
json.starts_at_human l(slot.starts_at, format: '%a, %H:%M')
json.title slot.approved_event.title
json.speakers do
json.array! slot.approved_event.participants_with_personal_profiles do |participant|
json.name participant.name
json.email participant.public_email
end
end
end
end
end
end
end
end

View File

@ -1,6 +1,6 @@
@speakers.each do |speaker| @speakers.each do |speaker|
json.set! speaker.user_id do json.set! speaker.user_id do
json.extract! speaker, :twitter, :github, :biography, :public_email, :organisation, :last_name, :first_name json.extract! speaker, :twitter, :github, :biography, :public_email, :organisation, :last_name, :first_name, :name
json.picture speaker.picture.serializable_hash json.picture rails_blob_url(speaker.picture.variant(resize_to_fill: [100, 100]))
end end
end end

View File

@ -11,7 +11,5 @@
С приложения QR код към този имейл ще можете да достъпите формуляра за обратна връзка на предложеното от Вас събитие. Моля, включете го в презентацията си. С приложения QR код към този имейл ще можете да достъпите формуляра за обратна връзка на предложеното от Вас събитие. Моля, включете го в презентацията си.
Ако планирате да представяте отдалечено или използвате MacBook, моля, отговорете на този имейл, за да ни уведомите. Също така, ако имате въпроси или нужда от допълнителна информация, не се колебайте да се свържете с нас, като отговорите на този имейл.
Поздрави, Поздрави,
Екипът на <%= @event.conference.title %> Екипът на <%= @event.conference.title %>

View File

@ -11,8 +11,6 @@ To confirm your participation please follow the link below as soon as you can:
We kindly request that you ensure the inclusion of the QR code attached to this email in your presentation. It links directly to the feedback form for your presentation, making it easier for attendees to provide valuable insights. We kindly request that you ensure the inclusion of the QR code attached to this email in your presentation. It links directly to the feedback form for your presentation, making it easier for attendees to provide valuable insights.
Additionally, please respond to this email to let us know whether your presentation will be remote or if you plan to present using a MacBook.
Should you have any questions or require further information, please do not hesitate to contact us by replying to this email. Should you have any questions or require further information, please do not hesitate to contact us by replying to this email.
Best regards, Best regards,

View File

@ -20,5 +20,6 @@
= f.input :food_preferences, collection: Volunteer::FOOD_PREFERENCES, wrapper: :horizontal_radio_and_checkboxes, as: :radio_buttons, checked: (@volunteer.food_preferences.presence || :none) = f.input :food_preferences, collection: Volunteer::FOOD_PREFERENCES, wrapper: :horizontal_radio_and_checkboxes, as: :radio_buttons, checked: (@volunteer.food_preferences.presence || :none)
= f.input :previous_experience = f.input :previous_experience
= f.input :notes = f.input :notes
= f.input :terms_accepted
.panel-footer.text-right .panel-footer.text-right
= f.submit class: 'btn btn-primary' = f.submit class: 'btn btn-primary'

View File

@ -3,7 +3,7 @@
<%- @volunteers.each do |volunteer| -%> <%- @volunteers.each do |volunteer| -%>
<%= CSV.generate_line([volunteer.id, <%= CSV.generate_line([volunteer.id,
volunteer.name, volunteer.name,
volunteer.email, "#{volunteer.confirmed_at.nil? ? '(unverified) ' : ''}#{volunteer.email}",
volunteer.language, volunteer.language,
volunteer.unique_id, volunteer.unique_id,
volunteer.phone, volunteer.phone,

View File

@ -48,7 +48,7 @@
h4.media-heading h4.media-heading
= volunteer.name = volunteer.name
p p
= icon(:envelope, volunteer.email) = icon(volunteer.confirmed_at.present? ? "envelope" : "envelope-o", "#{volunteer.confirmed_at.nil? ? '(unverified) ' : ''}#{volunteer.email}")
td td
= volunteer.volunteer_team.name = volunteer.volunteer_team.name
td.actions td.actions

View File

@ -38,7 +38,7 @@
h4 = t '.other_info' h4 = t '.other_info'
= icon(:language, t("locales.#{@volunteer.language}")) = icon(:language, t("locales.#{@volunteer.language}"))
br br
= icon(:envelope, @volunteer.email) = icon(@volunteer.confirmed_at.present? ? "envelope" : "envelope-o", @volunteer.email)
br br
= icon(:phone, @volunteer.phone.try(:phony_formatted, format: :international)) = icon(:phone, @volunteer.phone.try(:phony_formatted, format: :international))
br br

View File

@ -1,21 +1,11 @@
Здравейте, Здравейте,
<%= @volunteer.name %> <<%= @volunteer.email %>> изпрати кандидатура за доброволец. Някой изпрати кандидатура за доброволец.
Екипи: <%= @volunteer.volunteer_teams.map(&:name).join(', ') %> Екип: <%= @volunteer.volunteer_team.name %>
Връзка към профил: <%= management_conference_volunteer_url(@volunteer.conference, @volunteer) %>
Език: <%= @volunteer.language %> Език: <%= @volunteer.language %>
Телефон: <%= @volunteer.phone %>
Размер на тениска: <%= @volunteer.tshirt_size %> Размер на тениска: <%= @volunteer.tshirt_size %>
Кройка на тениска: <%= @volunteer.tshirt_cut %> Кройка на тениска: <%= @volunteer.tshirt_cut %>
Предпочитания за храна: <%= @volunteer.food_preferences %>
<% if @volunteer.previous_experience.present? -%>
Предходен опит:
<%= @volunteer.previous_experience %>
<% end -%>
<% if @volunteer.notes.present? -%>
Бележки:
<%= @volunteer.notes %>
<% end -%>

5
bin/thrust Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env ruby
require "rubygems"
require "bundler/setup"
load Gem.bin_path("thruster", "thrust")

View File

@ -79,7 +79,7 @@ test:
# #
production: production:
<<: *default <<: *default
host: host.containers.internal host: <%= ENV.fetch("CLARION_DATABASE_HOST") { "host.containers.internal" } %>
database: clarion database: clarion
username: clarion username: clarion
password: <%= ENV["CLARION_DATABASE_PASSWORD"] %> password: <%= ENV["CLARION_DATABASE_PASSWORD"] %>

View File

@ -39,7 +39,7 @@ set :linked_dirs, fetch(:linked_dirs, []).push("log", "tmp/uploads", "tmp/pids",
# Default value for keep_releases is 5 # Default value for keep_releases is 5
set :keep_releases, 550 set :keep_releases, 550
set :rvm_ruby_version, "2.6.5" set :rvm_ruby_version, "3.3.5"
set :puma_bind, ["tcp://127.0.0.1:9087"] set :puma_bind, ["tcp://127.0.0.1:9087"]
set :puma_init_active_record, true set :puma_init_active_record, true

View File

@ -46,14 +46,14 @@ Rails.application.configure do
# Assume all access to the app is happening through a SSL-terminating reverse proxy. # Assume all access to the app is happening through a SSL-terminating reverse proxy.
# Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.
# config.assume_ssl = true config.assume_ssl = true
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true config.force_ssl = ENV["CLARION_USE_PLAINTEXT"] != "yes"
# Log to STDOUT by default # Log to STDOUT by default
config.logger = ActiveSupport::Logger.new(STDOUT) config.logger = ActiveSupport::Logger.new(STDOUT)
.tap { |logger| logger.formatter = ::Logger::Formatter.new } .tap { |logger| logger.formatter = ::Logger::Formatter.new }
.then { |logger| ActiveSupport::TaggedLogging.new(logger) } .then { |logger| ActiveSupport::TaggedLogging.new(logger) }
# Prepend all log lines with the following tags. # Prepend all log lines with the following tags.
@ -90,7 +90,7 @@ Rails.application.configure do
config.action_mailer.default_options = {from: "OpenFest <cfp@openfest.org>"} config.action_mailer.default_options = {from: "OpenFest <cfp@openfest.org>"}
config.action_mailer.default_url_options = {host: "cfp.openfest.org"} config.action_mailer.default_url_options = {host: "cfp.openfest.org"}
config.action_mailer.smtp_settings = { config.action_mailer.smtp_settings = {
address: "mail.openfest.org" address: ENV.fetch("CLARION_MAIL_SERVER", "mail.openfest.org")
} }
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to # Enable locale fallbacks for I18n (makes lookups for any locale fall back to

View File

@ -118,6 +118,7 @@ bg:
food_preferences: Предпочитана храна food_preferences: Предпочитана храна
previous_experience: Предишен опит previous_experience: Предишен опит
notes: Бележки notes: Бележки
terms_accepted: Съгласен съм екипът да се свързва с мен
language: Език language: Език
volunteer_team: Екип доброволци volunteer_team: Екип доброволци
additional_volunteer_teams: Допълнителни екипи доброволци additional_volunteer_teams: Допълнителни екипи доброволци

View File

@ -0,0 +1,147 @@
bg:
activerecord:
attributes:
user:
confirmation_sent_at: Потвърждението е изпратено на
confirmation_token: Код за потвърждение
confirmed_at: Дата на потвърждение
created_at: Създаван на
current_password: Настояща парола
current_sign_in_at: Текущо влизане на
current_sign_in_ip: IP на текущото влизане
email: Имейл
encrypted_password: Закодирана парола
failed_attempts: Грешни опити
last_sign_in_at: Последно влизане на
last_sign_in_ip: IP на последното влизане
locked_at: Дата на заключването
password: Парола
password_confirmation: Потвърждаване на паролата
remember_created_at: Дата на създаване на запомнянето
remember_me: Запомни ме
reset_password_sent_at: Дата на изпращане на промяна на парола
reset_password_token: Код за промяна на парола
sign_in_count: Бройка входове
unconfirmed_email: Непотвърден имейл
unlock_token: Код за отключване на профил
updated_at: Последна промяна
models:
user:
one: Потребител
other: Потребители
devise:
confirmations:
confirmed: Регистрацията Ви е потвърдена.
new:
resend_confirmation_instructions: Повторно изпращане на инструкции за потвърждаване
send_instructions: Ще получите имейл с инструкции за потвърждаване на Вашата регистрация до няколко минути.
send_paranoid_instructions: Ако Вашият имейл адрес съществува в нашата база данни, ще получите имейл с инструкции за потвърждаване на Вашия профил до няколко минути.
failure:
already_authenticated: Вече сте влезли в профила си.
inactive: Профилът Ви все още не е активиран.
invalid: Грешни %{authentication_keys} или парола.
last_attempt: Имате още един опит преди акаунтът Ви да бъде заключен.
locked: Профилът Ви е заключен.
not_found_in_database: Грешни %{authentication_keys} или парола.
timeout: Сесията Ви е изтекла, моля влезте отново, за да продължите.
unauthenticated: Преди да продължите, трябва да влезете в профила си или да се регистрирате.
unconfirmed: Преди да продължите, трябва да потвърдите регистрацията си.
mailer:
confirmation_instructions:
action: Потвърждаване на профил
greeting: Добре дошли, %{recipient}!
instruction: 'Можете да потвърдите имейл адреса си чрез линка по-долу:'
subject: Инструкции за потвърждаване
email_changed:
greeting: Здравейте, %{recipient}!
message: Свързваме се с Вас, за да Ви уведомим, че имейлът Ви е сменен на %{email}.
message_unconfirmed: Свързваме се с Вас, за да Ви уведомим, че имейлът Ви се сменя на %{email}.
subject: Промяна на имейл
password_change:
greeting: Здравейте, %{recipient}!
message: Пишем Ви, за да Ви уведомим, че паролата Ви беше променена.
subject: Променена парола
reset_password_instructions:
action: Смяна на парола
greeting: Здравейте, %{recipient}!
instruction: Някой е поискал линк за промяна на паролата Ви. Можете да промените паролата си чрез линка по-долу.
instruction_2: В случай че не сте поискали промяна на паролата си, просто игнорирайте този имейл.
instruction_3: Паролата Ви няма да бъде променена, докато не използвате горния линк, за да създадете нова.
subject: Инструкции за промяна на парола
unlock_instructions:
action: Отключване на профил
greeting: Здравейте, %{recipient}!
instruction: 'Кликнете върху линка по-долу, за да отключите профила си:'
message: Профилът Ви е заключен поради надвишаване на максималния позволен брой неуспешни опити за вход.
subject: Инструкции за отключване
omniauth_callbacks:
failure: Не успяхме да ви оторизираме чрез %{kind}, защото %{reason}.
success: Успешна оторизация чрез %{kind} профил.
passwords:
edit:
change_my_password: Промяна на паролата ми
change_your_password: Променете паролата си
confirm_new_password: Потвърждение на новата парола
new_password: Нова парола
new:
forgot_your_password: Забравена парола?
send_me_reset_password_instructions: Изпрати инструкции за промяна на парола
no_token: Може да достъпите тази страница само от имейл за промяна на паролата. Ако сте отворили тази страница от такъв имейл, уверете се, че използвате целия URL-адрес, който сме Ви изпратили.
send_instructions: Ще получите имейл с инструкции как да промените паролата си до няколко минути.
send_paranoid_instructions: Ако Вашият имейл адрес съществува в базата ни, ще получите инструкции за промяна на Вашата парола.
updated: Паролата Ви е променена успешно. Влязохте успешно в профила си.
updated_not_active: Паролата Ви е променена успешно.
registrations:
destroyed: Довиждане! Вашият профил беше успешно изтрит. Надяваме се скоро да Ви видим отново.
edit:
are_you_sure: Сигурни ли сте?
cancel_my_account: Закриване на профил
currently_waiting_confirmation_for_email: 'Очаква се потвърждение за: %{email}'
leave_blank_if_you_don_t_want_to_change_it: оставете празно, ако не искате да го променяте
title: Редакция на %{resource}
unhappy: Недоволни
update: Обновяване
we_need_your_current_password_to_confirm_your_changes: въведете настоящата си парола за потвърждаване на промените
new:
sign_up: Регистрация
signed_up: Добре дошли! Вие се регистрирахте успешно.
signed_up_but_inactive: Регистрирахте се успешно. Въпреки това, не можете да влезете в профила си, защото той все още не е активиран.
signed_up_but_locked: Регистрирахте се успешно. Въпреки това, не можете да влезете в профила си, защото той е заключен.
signed_up_but_unconfirmed: Писмо с линк за потвърждаване на профила Ви беше изпратено на вашия имейл адрес. Моля, отворете линка, за да активирате Вашия профил.
update_needs_confirmation: Обновихте профила си успешно, но трябва да проверим Вашия нов имейл адрес. Моля, проверете пощата си и отворете линка за потвърждаване на новия адрес.
updated: Обновихте профила си успешно.
updated_but_not_signed_in: Вашият профил бе обновен успешно, но понеже паролата Ви беше променена, трябва да влезнете отново.
sessions:
already_signed_out: Излязохте успешно.
new:
sign_in: Вход
signed_in: Влязохте успешно.
signed_out: Излязохте успешно.
shared:
links:
back: Назад
didn_t_receive_confirmation_instructions: Не сте получили инструкции за потвърждаване?
didn_t_receive_unlock_instructions: Не сте получили инструкции за отключване?
forgot_your_password: Забравена парола?
sign_in: Вход
sign_in_with_provider: Вход с %{provider}
sign_up: Регистрация
minimum_password_length:
one: "(минимум %{count} символ)"
other: "(минимум %{count} символа)"
unlocks:
new:
resend_unlock_instructions: Повторно изпращане на инструкции за отключване
send_instructions: Ще получите имейл с инструкции как да отключите профила си до няколко минути.
send_paranoid_instructions: Ако профилът Ви съществува в базата ни, ще получите имейл с инструкции за отключването му до няколко минути.
unlocked: Профилът Ви е отключен успешно. За да продължите, моля влезте.
errors:
messages:
already_confirmed: е вече потвърден, моля опитайте да влезете в профила си
confirmation_period_expired: трябва да се потвърди в рамките на %{period}, моля направете нова заявка за потвърждаване
expired: е изтекъл, моля заявете нов
not_found: не е намерен
not_locked: не бе заключен
not_saved:
one: 'Една грешка попречи този %{resource} да бъде записан:'
other: "%{count} грешки попречиха този %{resource} да бъде записан:"

View File

@ -118,6 +118,7 @@ en:
food_preferences: Food preference food_preferences: Food preference
previous_experience: Previous experience previous_experience: Previous experience
notes: Notes notes: Notes
terms_accepted: I agree to be contacted by the team
language: Language language: Language
volunteer_team: Volunteer team volunteer_team: Volunteer team
additional_volunteer_teams: Additional volunteer teams additional_volunteer_teams: Additional volunteer teams

View File

@ -32,6 +32,7 @@ Rails.application.routes.draw do
resources :event_types, only: :index resources :event_types, only: :index
resources :halls, only: :index resources :halls, only: :index
resources :slots, only: :index resources :slots, only: :index
resource :schedule, only: :show
resources :volunteers resources :volunteers
end end
end end

View File

@ -0,0 +1,5 @@
class AddTermsAcceptedToVolunteer < ActiveRecord::Migration[7.1]
def change
add_column :volunteers, :terms_accepted, :boolean, default: false
end
end

View File

@ -0,0 +1,5 @@
class AddOwnerFieldToUsers < ActiveRecord::Migration[7.1]
def change
add_column :users, :owner, :boolean, null: false, default: false
end
end

View File

@ -11,4 +11,5 @@ User.create(
password_confirmation: "123qweASD", password_confirmation: "123qweASD",
confirmed_at: Time.current, confirmed_at: Time.current,
admin: true admin: true
owner: true
) )

1997
db/structure.sql Normal file

File diff suppressed because it is too large Load Diff

40
docker-compose.yml Normal file
View File

@ -0,0 +1,40 @@
services:
clarion:
build:
dockerfile: Dockerfile
context: .
environment:
RAILS_ENV: production
CLARION_DATABASE_HOST: db
CLARION_DATABASE_PASSWORD: not-a-real-db-password-tiliez4phei9oZoo1Shoyitee2zoon8O
REDIS_URL: redis://redis:6379/1
CLARION_MAIL_SERVER: mail-dummy
CLARION_USE_PLAINTEXT: yes
SECRET_KEY_BASE: not-a-real-secret-oodeig8etho1usik5Eehoh9jah9yuS3o
ports:
- 127.0.0.1:3000:3000
depends_on:
- db
- redis
- mail-dummy
db:
image: postgres:16.3
environment:
POSTGRES_USER: clarion
POSTGRES_PASSWORD: not-a-real-db-password-tiliez4phei9oZoo1Shoyitee2zoon8O
POSTGRES_DB: clarion
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7
mail-dummy:
image: python:3.11-slim
command: python3 -m smtpd --debug --class DebuggingServer 0.0.0.0:25
expose:
- 25
volumes:
pgdata:

View File

@ -22,5 +22,6 @@
= f.input :food_preferences, collection: Volunteer::FOOD_PREFERENCES, as: :radio_buttons, wrapper: :default, checked: (@volunteer.food_preferences.presence || :none) = f.input :food_preferences, collection: Volunteer::FOOD_PREFERENCES, as: :radio_buttons, wrapper: :default, checked: (@volunteer.food_preferences.presence || :none)
= f.input :previous_experience = f.input :previous_experience
= f.input :notes = f.input :notes
= f.input :terms_accepted
.form-actions .form-actions
= f.button :submit = f.button :submit

View File

@ -11,6 +11,7 @@ FactoryBot.define do
factory :administrator do factory :administrator do
admin { true } admin { true }
owner { true }
end end
end end
end end