diff --git a/.annotaterb.yml b/.annotaterb.yml
new file mode 100644
index 0000000..2811be7
--- /dev/null
+++ b/.annotaterb.yml
@@ -0,0 +1,58 @@
+---
+:position: before
+:position_in_additional_file_patterns: before
+:position_in_class: before
+:position_in_factory: before
+:position_in_fixture: before
+:position_in_routes: before
+:position_in_serializer: before
+:position_in_test: before
+:classified_sort: true
+:exclude_controllers: true
+:exclude_factories: false
+:exclude_fixtures: false
+:exclude_helpers: true
+:exclude_scaffolds: true
+:exclude_serializers: false
+:exclude_sti_subclasses: false
+:exclude_tests: false
+:force: false
+:format_markdown: false
+:format_rdoc: false
+:format_yard: false
+:frozen: false
+:ignore_model_sub_dir: false
+:ignore_unknown_models: false
+:include_version: false
+:show_check_constraints: false
+:show_complete_foreign_keys: false
+:show_foreign_keys: true
+:show_indexes: true
+:simple_indexes: false
+:sort: false
+:timestamp: false
+:trace: false
+:with_comment: true
+:with_column_comments: true
+:with_table_comments: true
+:active_admin: false
+:command:
+:debug: false
+:hide_default_column_types: ''
+:hide_limit_column_types: ''
+:ignore_columns:
+:ignore_routes:
+:models: true
+:routes: false
+:skip_on_db_migrate: false
+:target_action: :do_annotations
+:wrapper:
+:wrapper_close:
+:wrapper_open:
+:classes_default_to_s: []
+:additional_file_patterns: []
+:model_dir:
+- app/models
+:require: []
+:root_dir:
+- ''
diff --git a/Gemfile b/Gemfile
index cf2c6b6..28f7d7e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -40,6 +40,7 @@ group :development, :test do
end
group :development do
+ gem "annotaterb", require: false
# Use console on exceptions pages [https://github.com/rails/web-console]
gem "web-console"
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 69051a2..d10639c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -74,6 +74,7 @@ GEM
uri (>= 0.13.1)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
+ annotaterb (4.14.0)
base64 (0.2.0)
benchmark (0.4.0)
bigdecimal (3.1.9)
@@ -282,6 +283,7 @@ PLATFORMS
x86_64-linux-musl
DEPENDENCIES
+ annotaterb
bootsnap
brakeman
capybara
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 0d95db2..ffa3f27 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,4 +1,16 @@
class ApplicationController < ActionController::Base
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
allow_browser versions: :modern
+
+ before_action :require_authorization
+
+ def authorized?
+ cookies.signed[:_entrance_exam_authorized].present?
+ end
+
+ private
+
+ def require_authorization
+ redirect_to new_sessions_path unless authorized?
+ end
end
diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb
new file mode 100644
index 0000000..ae80659
--- /dev/null
+++ b/app/controllers/questions_controller.rb
@@ -0,0 +1,23 @@
+class QuestionsController < ApplicationController
+ def answer
+ question = Question.find(params[:id])
+ @answer = Answer.find_or_initialize_by(question:)
+
+ send(:"handle_#{question.answer_kind}_answer")
+ end
+
+ private
+
+ def handle_simple_answer
+ @answer.update!(data: params[:value])
+ end
+
+ def handle_image_answer
+ @answer.image.attach(
+ io: StringIO.new(Base64.decode64(params[:data])),
+ filename: params[:filename],
+ content_type: params[:mimetype],
+ )
+ @answer.save!
+ end
+end
diff --git a/app/controllers/sections_controller.rb b/app/controllers/sections_controller.rb
new file mode 100644
index 0000000..7ad9a61
--- /dev/null
+++ b/app/controllers/sections_controller.rb
@@ -0,0 +1,10 @@
+class SectionsController < ApplicationController
+ def index
+ @sections = Section.all
+ end
+
+ def show
+ @section = Section.find(params[:id])
+ @questions = @section.questions.includes(:answer)
+ end
+end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
new file mode 100644
index 0000000..02f73e3
--- /dev/null
+++ b/app/controllers/sessions_controller.rb
@@ -0,0 +1,25 @@
+class SessionsController < ApplicationController
+ skip_before_action :require_authorization
+
+ def new
+ redirect_to sections_path if authorized?
+ end
+
+ def create
+ if authorized?
+ redirect_to sections_path
+ return
+ end
+
+ if Rails.configuration.entrance_exam_token != params[:token]
+ redirect_to new_sessions_path
+ return
+ end
+
+ cookies.signed[:_entrance_exam_authorized] = {
+ value: true,
+ expires: 1.year
+ }
+ redirect_to sections_path
+ end
+end
diff --git a/app/helpers/questions_helper.rb b/app/helpers/questions_helper.rb
new file mode 100644
index 0000000..2eaab4a
--- /dev/null
+++ b/app/helpers/questions_helper.rb
@@ -0,0 +1,2 @@
+module QuestionsHelper
+end
diff --git a/app/helpers/sections_helper.rb b/app/helpers/sections_helper.rb
new file mode 100644
index 0000000..32f81ae
--- /dev/null
+++ b/app/helpers/sections_helper.rb
@@ -0,0 +1,2 @@
+module SectionsHelper
+end
diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb
new file mode 100644
index 0000000..309f8b2
--- /dev/null
+++ b/app/helpers/sessions_helper.rb
@@ -0,0 +1,2 @@
+module SessionsHelper
+end
diff --git a/app/javascript/application.js b/app/javascript/application.js
index db91aa8..b95e42c 100644
--- a/app/javascript/application.js
+++ b/app/javascript/application.js
@@ -1 +1,8 @@
// Entry point for the build script in your package.json
+import initSimpleQuestions from './simple_input.js'
+import initImageQuestions from './image_input.js'
+
+addEventListener("DOMContentLoaded", () => {
+ initSimpleQuestions()
+ initImageQuestions()
+})
diff --git a/app/javascript/image_input.js b/app/javascript/image_input.js
new file mode 100644
index 0000000..89a7f4e
--- /dev/null
+++ b/app/javascript/image_input.js
@@ -0,0 +1,35 @@
+const csrfToken = document.querySelector("[name='csrf-token']").content
+
+export default function initImageQuestions() {
+ document.querySelectorAll('[data-behaviour="question_image_input"]').forEach((input) => {
+ const submitUrl = input.dataset.submitUrl
+
+ input.addEventListener('change', () => {
+ const file = input.files[0];
+ const fileReader = new FileReader();
+ fileReader.onload = (ev) => {
+ fetch(submitUrl, {
+ method: 'PUT',
+ headers: {
+ "x-csrf-token": csrfToken,
+ "content-type": "application/json",
+ },
+ body: JSON.stringify({
+ filename: file.name,
+ mimetype: file.type,
+ data: ev.target.result.replace(/^data:[a-zA-Z0-9!#$%^&\\*_\-+{}|'.`~]+\/[a-zA-Z0-9!#$%^&\\*_\-+{}|'.`~]+;base64,/, ""),
+ })
+ })
+ }
+ fileReader.readAsDataURL(file);
+ const preview = document.querySelector(`#${input.id}_preview`)
+ while (preview.firstChild) {
+ preview.removeChild(preview.firstChild)
+ }
+ const previewImage = document.createElement("img")
+ previewImage.src = URL.createObjectURL(file)
+ previewImage.alt = previewImage.title = file.name
+ preview.appendChild(previewImage)
+ })
+ })
+}
diff --git a/app/javascript/simple_input.js b/app/javascript/simple_input.js
new file mode 100644
index 0000000..a217db0
--- /dev/null
+++ b/app/javascript/simple_input.js
@@ -0,0 +1,23 @@
+import debounce from 'debounce'
+
+const csrfToken = document.querySelector("[name='csrf-token']").content
+
+export default function initSimpleQuestions() {
+ document.querySelectorAll('[data-behaviour="question_simple_input"]').forEach((input) => {
+ const submitUrl = input.dataset.submitUrl
+
+ const handleInput = debounce(() => {
+ fetch(submitUrl, {
+ method: 'PUT',
+ headers: {
+ "x-csrf-token": csrfToken,
+ "content-type": "application/json",
+ },
+ body: JSON.stringify({ value: input.value }),
+ })
+ }, 300)
+
+ input.addEventListener('change', handleInput)
+ input.addEventListener('input', handleInput)
+ })
+}
diff --git a/app/models/answer.rb b/app/models/answer.rb
index 7377412..8fc352b 100644
--- a/app/models/answer.rb
+++ b/app/models/answer.rb
@@ -1,3 +1,25 @@
+# == Schema Information
+#
+# Table name: answers
+#
+# id :integer not null, primary key
+# data :text
+# created_at :datetime not null
+# updated_at :datetime not null
+# question_id :integer not null
+#
+# Indexes
+#
+# index_answers_on_question_id (question_id)
+#
+# Foreign Keys
+#
+# question_id (question_id => questions.id)
+#
class Answer < ApplicationRecord
belongs_to :question
+
+ has_one_attached :image
+
+ serialize :data, coder: JSON
end
diff --git a/app/models/question.rb b/app/models/question.rb
index 3a432e9..76896d0 100644
--- a/app/models/question.rb
+++ b/app/models/question.rb
@@ -1,3 +1,28 @@
+# == Schema Information
+#
+# Table name: questions
+#
+# id :integer not null, primary key
+# answer_kind :integer not null
+# public_asset_path :string
+# question_kind :integer default("simple"), not null
+# text :text not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# section_id :integer not null
+#
+# Indexes
+#
+# index_questions_on_section_id (section_id)
+#
+# Foreign Keys
+#
+# section_id (section_id => sections.id)
+#
class Question < ApplicationRecord
+ enum :answer_kind, { simple: 0, image: 1, politicians: 2 }, prefix: true
+ enum :question_kind, { simple: 0, video: 1 }, prefix: true
+
belongs_to :section
+ has_one :answer
end
diff --git a/app/models/section.rb b/app/models/section.rb
index ec21dc1..dbf5452 100644
--- a/app/models/section.rb
+++ b/app/models/section.rb
@@ -1,2 +1,13 @@
+# == Schema Information
+#
+# Table name: sections
+#
+# id :integer not null, primary key
+# description :text not null
+# title :text not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
class Section < ApplicationRecord
+ has_many :questions
end
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 0f56719..4d3a5e8 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -3,23 +3,18 @@
<%= content_for(:title) || "Entrance Exam" %>
-
-
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= yield :head %>
- <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
- <%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>
-
<%# Includes all stylesheet files in app/assets/stylesheets %>
<%= stylesheet_link_tag :app %>
- <%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
+ <%= javascript_include_tag "application" %>
diff --git a/app/views/pwa/manifest.json.erb b/app/views/pwa/manifest.json.erb
deleted file mode 100644
index cad26ad..0000000
--- a/app/views/pwa/manifest.json.erb
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "name": "EntranceExam",
- "icons": [
- {
- "src": "/icon.png",
- "type": "image/png",
- "sizes": "512x512"
- },
- {
- "src": "/icon.png",
- "type": "image/png",
- "sizes": "512x512",
- "purpose": "maskable"
- }
- ],
- "start_url": "/",
- "display": "standalone",
- "scope": "/",
- "description": "EntranceExam.",
- "theme_color": "red",
- "background_color": "red"
-}
diff --git a/app/views/pwa/service-worker.js b/app/views/pwa/service-worker.js
deleted file mode 100644
index b3a13fb..0000000
--- a/app/views/pwa/service-worker.js
+++ /dev/null
@@ -1,26 +0,0 @@
-// Add a service worker for processing Web Push notifications:
-//
-// self.addEventListener("push", async (event) => {
-// const { title, options } = await event.data.json()
-// event.waitUntil(self.registration.showNotification(title, options))
-// })
-//
-// self.addEventListener("notificationclick", function(event) {
-// event.notification.close()
-// event.waitUntil(
-// clients.matchAll({ type: "window" }).then((clientList) => {
-// for (let i = 0; i < clientList.length; i++) {
-// let client = clientList[i]
-// let clientPath = (new URL(client.url)).pathname
-//
-// if (clientPath == event.notification.data.path && "focus" in client) {
-// return client.focus()
-// }
-// }
-//
-// if (clients.openWindow) {
-// return clients.openWindow(event.notification.data.path)
-// }
-// })
-// )
-// })
diff --git a/app/views/questions/_image_form.html.erb b/app/views/questions/_image_form.html.erb
new file mode 100644
index 0000000..6870968
--- /dev/null
+++ b/app/views/questions/_image_form.html.erb
@@ -0,0 +1,12 @@
+">
+ <% if question.answer.present? %>
+ <%= image_tag question.answer.image %>
+ <% end %>
+
+"
+ type="file"
+ accept=".jpg,.jpeg,.png,.webp,image/jpg,image/jpeg,image/png,image/webp"
+ autocomplete="off"
+ data-behaviour="question_image_input"
+ data-submit-url="<%= answer_question_url(question.id) %>"
+/>
diff --git a/app/views/questions/_politicians_form.html.erb b/app/views/questions/_politicians_form.html.erb
new file mode 100644
index 0000000..6d0db74
--- /dev/null
+++ b/app/views/questions/_politicians_form.html.erb
@@ -0,0 +1,3 @@
+
+ This one's going to be hard...
+
diff --git a/app/views/questions/_simple_form.html.erb b/app/views/questions/_simple_form.html.erb
new file mode 100644
index 0000000..834e88b
--- /dev/null
+++ b/app/views/questions/_simple_form.html.erb
@@ -0,0 +1,7 @@
+"
+ type="text"
+ value="<%= question.answer&.data || '' %>"
+ autocomplete="off"
+ data-behaviour="question_simple_input"
+ data-submit-url="<%= answer_question_url(question.id) %>"
+/>
diff --git a/app/views/questions/_simple_show.html.erb b/app/views/questions/_simple_show.html.erb
new file mode 100644
index 0000000..256c94a
--- /dev/null
+++ b/app/views/questions/_simple_show.html.erb
@@ -0,0 +1 @@
+<%= question.text %>
diff --git a/app/views/questions/_video_show.html.erb b/app/views/questions/_video_show.html.erb
new file mode 100644
index 0000000..8f612d9
--- /dev/null
+++ b/app/views/questions/_video_show.html.erb
@@ -0,0 +1 @@
+
diff --git a/app/views/sections/index.html.erb b/app/views/sections/index.html.erb
new file mode 100644
index 0000000..056d6fd
--- /dev/null
+++ b/app/views/sections/index.html.erb
@@ -0,0 +1,5 @@
+<% @sections.each do |section| %>
+
+ <%= link_to section.title, section_url(section) %>
+
+<% end %>
diff --git a/app/views/sections/show.html.erb b/app/views/sections/show.html.erb
new file mode 100644
index 0000000..107eed8
--- /dev/null
+++ b/app/views/sections/show.html.erb
@@ -0,0 +1,10 @@
+<%= @section.title %>
+
+<%= @section.description %>
+
+<% @questions.each do |question| %>
+
+ <%= render partial: "questions/#{question.question_kind}_show", locals: { question: } %>
+ <%= render partial: "questions/#{question.answer_kind}_form", locals: { question: } %>
+
+<% end %>
diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb
new file mode 100644
index 0000000..d47fc7a
--- /dev/null
+++ b/app/views/sessions/new.html.erb
@@ -0,0 +1,5 @@
+<%= form_with url: sessions_path, method: :post do |form| %>
+ <%= form.label :token, "Token" %>
+ <%= form.password_field :token %>
+ <%= form.submit "Start exam" %>
+<% end %>
diff --git a/config/application.rb b/config/application.rb
index 89fc5c6..932ba42 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -35,5 +35,6 @@ module EntranceExam
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
+ config.entrance_exam_token = "password"
end
end
diff --git a/config/routes.rb b/config/routes.rb
index 48254e8..23a99af 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -10,5 +10,15 @@ Rails.application.routes.draw do
# get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker
# Defines the root path route ("/")
- # root "posts#index"
+ root "sessions#new"
+
+ resources "sessions", only: [:new, :create]
+
+ resources "sections", only: [:index, :show]
+
+ resources "questions", only: [] do
+ member do
+ put :answer
+ end
+ end
end
diff --git a/db/migrate/20250426183359_create_active_storage_tables.active_storage.rb b/db/migrate/20250426183359_create_active_storage_tables.active_storage.rb
new file mode 100644
index 0000000..6bd8bd0
--- /dev/null
+++ b/db/migrate/20250426183359_create_active_storage_tables.active_storage.rb
@@ -0,0 +1,57 @@
+# This migration comes from active_storage (originally 20170806125915)
+class CreateActiveStorageTables < ActiveRecord::Migration[7.0]
+ def change
+ # Use Active Record's configured type for primary and foreign keys
+ primary_key_type, foreign_key_type = primary_and_foreign_key_types
+
+ create_table :active_storage_blobs, id: primary_key_type do |t|
+ t.string :key, null: false
+ t.string :filename, null: false
+ t.string :content_type
+ t.text :metadata
+ t.string :service_name, null: false
+ t.bigint :byte_size, null: false
+ t.string :checksum
+
+ if connection.supports_datetime_with_precision?
+ t.datetime :created_at, precision: 6, null: false
+ else
+ t.datetime :created_at, null: false
+ end
+
+ t.index [ :key ], unique: true
+ end
+
+ create_table :active_storage_attachments, id: primary_key_type do |t|
+ t.string :name, null: false
+ t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
+ t.references :blob, null: false, type: foreign_key_type
+
+ if connection.supports_datetime_with_precision?
+ t.datetime :created_at, precision: 6, null: false
+ else
+ t.datetime :created_at, null: false
+ end
+
+ t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true
+ t.foreign_key :active_storage_blobs, column: :blob_id
+ end
+
+ create_table :active_storage_variant_records, id: primary_key_type do |t|
+ t.belongs_to :blob, null: false, index: false, type: foreign_key_type
+ t.string :variation_digest, null: false
+
+ t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true
+ t.foreign_key :active_storage_blobs, column: :blob_id
+ end
+ end
+
+ private
+ def primary_and_foreign_key_types
+ config = Rails.configuration.generators
+ setting = config.options[config.orm][:primary_key_type]
+ primary_key_type = setting || :primary_key
+ foreign_key_type = setting || :bigint
+ [ primary_key_type, foreign_key_type ]
+ end
+end
diff --git a/db/migrate/20250426184110_answer_data_nullable.rb b/db/migrate/20250426184110_answer_data_nullable.rb
new file mode 100644
index 0000000..5f38aca
--- /dev/null
+++ b/db/migrate/20250426184110_answer_data_nullable.rb
@@ -0,0 +1,5 @@
+class AnswerDataNullable < ActiveRecord::Migration[8.0]
+ def change
+ change_column_null :answers, :data, true
+ end
+end
diff --git a/db/migrate/20250426185112_question_rename_type_kind.rb b/db/migrate/20250426185112_question_rename_type_kind.rb
new file mode 100644
index 0000000..c975d8b
--- /dev/null
+++ b/db/migrate/20250426185112_question_rename_type_kind.rb
@@ -0,0 +1,5 @@
+class QuestionRenameTypeKind < ActiveRecord::Migration[8.0]
+ def change
+ rename_column :questions, :type, :kind
+ end
+end
diff --git a/db/migrate/20250426200114_question_question_kind.rb b/db/migrate/20250426200114_question_question_kind.rb
new file mode 100644
index 0000000..5216354
--- /dev/null
+++ b/db/migrate/20250426200114_question_question_kind.rb
@@ -0,0 +1,6 @@
+class QuestionQuestionKind < ActiveRecord::Migration[8.0]
+ def change
+ rename_column :questions, :kind, :answer_kind
+ add_column :questions, :question_kind, :integer, null: false, default: 0
+ end
+end
diff --git a/db/migrate/20250426202311_question_asset_name.rb b/db/migrate/20250426202311_question_asset_name.rb
new file mode 100644
index 0000000..0d71b8b
--- /dev/null
+++ b/db/migrate/20250426202311_question_asset_name.rb
@@ -0,0 +1,5 @@
+class QuestionAssetName < ActiveRecord::Migration[8.0]
+ def change
+ add_column :questions, :asset_name, :string, null: true
+ end
+end
diff --git a/db/migrate/20250426202909_question_rename_asset_name_public_asset_path.rb b/db/migrate/20250426202909_question_rename_asset_name_public_asset_path.rb
new file mode 100644
index 0000000..ea6735d
--- /dev/null
+++ b/db/migrate/20250426202909_question_rename_asset_name_public_asset_path.rb
@@ -0,0 +1,5 @@
+class QuestionRenameAssetNamePublicAssetPath < ActiveRecord::Migration[8.0]
+ def change
+ rename_column :questions, :asset_name, :public_asset_path
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
new file mode 100644
index 0000000..838462e
--- /dev/null
+++ b/db/schema.rb
@@ -0,0 +1,72 @@
+# 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.0].define(version: 2025_04_26_202909) do
+ create_table "active_storage_attachments", force: :cascade do |t|
+ t.string "name", null: false
+ t.string "record_type", null: false
+ t.bigint "record_id", null: false
+ t.bigint "blob_id", null: false
+ t.datetime "created_at", null: false
+ t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
+ t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
+ end
+
+ create_table "active_storage_blobs", force: :cascade do |t|
+ t.string "key", null: false
+ t.string "filename", null: false
+ t.string "content_type"
+ t.text "metadata"
+ t.string "service_name", null: false
+ t.bigint "byte_size", null: false
+ t.string "checksum"
+ t.datetime "created_at", null: false
+ t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
+ end
+
+ create_table "active_storage_variant_records", force: :cascade do |t|
+ t.bigint "blob_id", null: false
+ t.string "variation_digest", null: false
+ t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
+ end
+
+ create_table "answers", force: :cascade do |t|
+ t.integer "question_id", null: false
+ t.text "data"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["question_id"], name: "index_answers_on_question_id"
+ end
+
+ create_table "questions", force: :cascade do |t|
+ t.integer "section_id", null: false
+ t.text "text", null: false
+ t.integer "answer_kind", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "question_kind", default: 0, null: false
+ t.string "public_asset_path"
+ t.index ["section_id"], name: "index_questions_on_section_id"
+ end
+
+ create_table "sections", force: :cascade do |t|
+ t.text "title", null: false
+ t.text "description", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
+ add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
+ add_foreign_key "answers", "questions"
+ add_foreign_key "questions", "sections"
+end
diff --git a/db/seeds.rb b/db/seeds.rb
index 4fbd6ed..c6ed40b 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -7,3 +7,5 @@
# ["Action", "Comedy", "Drama", "Horror"].each do |genre_name|
# MovieGenre.find_or_create_by!(name: genre_name)
# end
+
+Rake.application["db:fixtures:load"].invoke
diff --git a/lib/tasks/annotate_rb.rake b/lib/tasks/annotate_rb.rake
new file mode 100644
index 0000000..1ad0ec3
--- /dev/null
+++ b/lib/tasks/annotate_rb.rake
@@ -0,0 +1,8 @@
+# This rake task was added by annotate_rb gem.
+
+# Can set `ANNOTATERB_SKIP_ON_DB_TASKS` to be anything to skip this
+if Rails.env.development? && ENV["ANNOTATERB_SKIP_ON_DB_TASKS"].nil?
+ require "annotate_rb"
+
+ AnnotateRb::Core.load_rake_tasks
+end
diff --git a/package.json b/package.json
index b90b0de..e4646f7 100644
--- a/package.json
+++ b/package.json
@@ -1,18 +1,19 @@
{
- "name": "app",
- "private": true,
- "devDependencies": {
- "esbuild": "^0.25.3"
- },
- "scripts": {
- "build": "esbuild app/javascript/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets",
- "build:css": "postcss ./app/assets/stylesheets/application.postcss.css -o ./app/assets/builds/application.css"
- },
- "dependencies": {
- "autoprefixer": "^10.4.21",
- "postcss": "^8.5.3",
- "postcss-cli": "^11.0.1",
- "postcss-import": "^16.1.0",
- "postcss-nesting": "^13.0.1"
- }
+ "name": "app",
+ "private": true,
+ "scripts": {
+ "build:js": "esbuild app/javascript/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets",
+ "build:css": "postcss ./app/assets/stylesheets/application.postcss.css -o ./app/assets/builds/application.css"
+ },
+ "dependencies": {
+ "autoprefixer": "^10.4.21",
+ "debounce": "^2.2.0",
+ "postcss": "^8.5.3",
+ "postcss-cli": "^11.0.1",
+ "postcss-import": "^16.1.0",
+ "postcss-nesting": "^13.0.1"
+ },
+ "devDependencies": {
+ "esbuild": "^0.25.3"
+ }
}
diff --git a/public/videos/els_en_geert.mp4 b/public/videos/els_en_geert.mp4
new file mode 100644
index 0000000..6f41abe
Binary files /dev/null and b/public/videos/els_en_geert.mp4 differ
diff --git a/public/videos/stijn.mp4 b/public/videos/stijn.mp4
new file mode 100644
index 0000000..f5595d3
Binary files /dev/null and b/public/videos/stijn.mp4 differ
diff --git a/test/controllers/questions_controller_test.rb b/test/controllers/questions_controller_test.rb
new file mode 100644
index 0000000..e27ad05
--- /dev/null
+++ b/test/controllers/questions_controller_test.rb
@@ -0,0 +1,7 @@
+require "test_helper"
+
+class QuestionsControllerTest < ActionDispatch::IntegrationTest
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/test/controllers/sections_controller_test.rb b/test/controllers/sections_controller_test.rb
new file mode 100644
index 0000000..ae252a1
--- /dev/null
+++ b/test/controllers/sections_controller_test.rb
@@ -0,0 +1,7 @@
+require "test_helper"
+
+class SectionsControllerTest < ActionDispatch::IntegrationTest
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/test/controllers/sessions_controller_test.rb b/test/controllers/sessions_controller_test.rb
new file mode 100644
index 0000000..e5da4b3
--- /dev/null
+++ b/test/controllers/sessions_controller_test.rb
@@ -0,0 +1,7 @@
+require "test_helper"
+
+class SessionsControllerTest < ActionDispatch::IntegrationTest
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/test/fixtures/answers.yml b/test/fixtures/answers.yml
index e7a896e..1003d8c 100644
--- a/test/fixtures/answers.yml
+++ b/test/fixtures/answers.yml
@@ -1,9 +1,20 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
-one:
- question: one
- data: MyText
-
-two:
- question: two
- data: MyText
+# == Schema Information
+#
+# Table name: answers
+#
+# id :integer not null, primary key
+# data :text
+# created_at :datetime not null
+# updated_at :datetime not null
+# question_id :integer not null
+#
+# Indexes
+#
+# index_answers_on_question_id (question_id)
+#
+# Foreign Keys
+#
+# question_id (question_id => questions.id)
+#
diff --git a/test/fixtures/questions.yml b/test/fixtures/questions.yml
index 81b7cdf..6490764 100644
--- a/test/fixtures/questions.yml
+++ b/test/fixtures/questions.yml
@@ -1,11 +1,151 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
-one:
- section: one
- text: MyText
- type: 1
+# == Schema Information
+#
+# Table name: questions
+#
+# id :integer not null, primary key
+# answer_kind :integer not null
+# public_asset_path :string
+# question_kind :integer default("simple"), not null
+# text :text not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# section_id :integer not null
+#
+# Indexes
+#
+# index_questions_on_section_id (section_id)
+#
+# Foreign Keys
+#
+# section_id (section_id => sections.id)
+#
+sport_bashir:
+ section: sport
+ text: In welke straat groeide Bashir Abdi op?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
-two:
- section: two
- text: MyText
- type: 1
+sport_club:
+ section: sport
+ text: In welke sport versloeg een Gentse club ooit een team uit Oxford?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+sport_sabbe:
+ section: sport
+ text: Waar ligt de Karel Sabbeberg?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+sport_aagent:
+ section: sport
+ text: Wat is de meest recente beker van Belgie die AA Gent gewonnen heeft waar Tomas niet in het stadion zat?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+sport_hockey:
+ section: sport
+ text: ""
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:video] %>
+ public_asset_path: "/videos/els_en_geert.mp4"
+
+sport_busschaert:
+ section: sport
+ text: Neem een foto op het bankje van Matthias Busschaert (en wandel een rondje rond de Watersportbaan).
+ answer_kind: <%= Question.answer_kinds[:image] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+sport_klimmen:
+ section: sport
+ text: Bemachtig een foto van Charlotte terwijl ze aan het klimmen is zonder dat ze het merkt.
+ answer_kind: <%= Question.answer_kinds[:image] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+life_haven:
+ section: life
+ text: Wat moet je 24u voor het binnenkomen van de haven in Gent doen?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+life_pendelaars:
+ section: life
+ text: Hoeveel pendelaars komen dagelijks van buiten Gent in Gent werken?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+life_repetitieruimtes:
+ section: life
+ text: Hoeveel repetitieruimtes en ateliers waren er vroeger in de Leopoldskazerne?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+life_stam:
+ section: life
+ text: Op welke tegel ligt de Leopoldskazerne in het STAM?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+life_bollekesschool:
+ section: life
+ text: Wat is de bollekesschool?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+life_langst:
+ section: life
+ text: Wie woont er al het langst in Gent?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+life_haven_oprichting:
+ section: life
+ text: ""
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:video] %>
+ public_asset_path: "/videos/stijn.mp4"
+
+politics_regenpijpen:
+ section: politics
+ text: Welke kleur hebben de regenpijpen van het stadhuis en waarom?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+politics_schepenen:
+ section: politics
+ text: Verbind de schepenen en hun bevoegdheden.
+ answer_kind: <%= Question.answer_kinds[:politicians] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+politics_anneleen:
+ section: politics
+ text: Hoeveel keer is Anneleen al op haar gezicht gegaan (fysiek en metaforisch)?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+politics_langste:
+ section: politics
+ text: Wie was de langste burgemeester van Gent?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+politics_langst:
+ section: politics
+ text: Wie was er het langst burgemeester van Gent?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+politics_nazi:
+ section: politics
+ text: Welke Gentse burgemeester is niet opgenomen in de officiƫle lijst van Gentse burgemeesters?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
+politics_jorg:
+ section: politics
+ text: Hoeveel stemmen kwam Jorg te kort om in de provincieraad te zetelen?
+ answer_kind: <%= Question.answer_kinds[:simple] %>
+ question_kind: <%= Question.question_kinds[:simple] %>
+
diff --git a/test/fixtures/sections.yml b/test/fixtures/sections.yml
index 393ad6b..1d48a2c 100644
--- a/test/fixtures/sections.yml
+++ b/test/fixtures/sections.yml
@@ -1,9 +1,23 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
-one:
- title: MyText
- description: MyText
+# == Schema Information
+#
+# Table name: sections
+#
+# id :integer not null, primary key
+# description :text not null
+# title :text not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+sport:
+ title: Sport
+ description: Gent is een sportieve stad, dus daarom enkele vragen daarover.
-two:
- title: MyText
- description: MyText
+life:
+ title: Leven, werken en wonen
+ description: In Gent moet er ook geleefd worden.
+
+politics:
+ title: Politiek
+ description: De Gentse politiek is interessanter dan de Duffelse!
diff --git a/test/models/answer_test.rb b/test/models/answer_test.rb
index 1c9d165..c1ee434 100644
--- a/test/models/answer_test.rb
+++ b/test/models/answer_test.rb
@@ -1,3 +1,21 @@
+# == Schema Information
+#
+# Table name: answers
+#
+# id :integer not null, primary key
+# data :text
+# created_at :datetime not null
+# updated_at :datetime not null
+# question_id :integer not null
+#
+# Indexes
+#
+# index_answers_on_question_id (question_id)
+#
+# Foreign Keys
+#
+# question_id (question_id => questions.id)
+#
require "test_helper"
class AnswerTest < ActiveSupport::TestCase
diff --git a/test/models/question_test.rb b/test/models/question_test.rb
index 50011db..035485b 100644
--- a/test/models/question_test.rb
+++ b/test/models/question_test.rb
@@ -1,3 +1,24 @@
+# == Schema Information
+#
+# Table name: questions
+#
+# id :integer not null, primary key
+# answer_kind :integer not null
+# public_asset_path :string
+# question_kind :integer default("simple"), not null
+# text :text not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# section_id :integer not null
+#
+# Indexes
+#
+# index_questions_on_section_id (section_id)
+#
+# Foreign Keys
+#
+# section_id (section_id => sections.id)
+#
require "test_helper"
class QuestionTest < ActiveSupport::TestCase
diff --git a/test/models/section_test.rb b/test/models/section_test.rb
index f6557b9..16d49c7 100644
--- a/test/models/section_test.rb
+++ b/test/models/section_test.rb
@@ -1,3 +1,13 @@
+# == Schema Information
+#
+# Table name: sections
+#
+# id :integer not null, primary key
+# description :text not null
+# title :text not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
require "test_helper"
class SectionTest < ActiveSupport::TestCase
diff --git a/yarn.lock b/yarn.lock
index 5269ee5..8f4d87c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -237,6 +237,11 @@ cssesc@^3.0.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
+debounce@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/debounce/-/debounce-2.2.0.tgz#f895fa2fbdb579a0f0d3dcf5dde19657e50eaad5"
+ integrity sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==
+
dependency-graph@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-1.0.0.tgz#bb5e85aec1310bc13b22dbd76e3196c4ee4c10d2"