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"