From 95aad3d649b7f2743bee0409e40d5bb948f60f71 Mon Sep 17 00:00:00 2001 From: hschne Date: Mon, 18 Dec 2023 11:56:39 +0100 Subject: [PATCH] Error pages and stuff --- Gemfile | 2 + Gemfile.lock | 6 ++ app/controllers/drops_controller.rb | 40 --------- app/controllers/errors_controller.rb | 9 ++ app/controllers/uploads_controller.rb | 40 ++++++--- app/helpers/errors_helper.rb | 2 + app/javascript/application.js | 6 +- app/models/dice_word.rb | 11 --- app/models/upload.rb | 31 +++++++ app/views/application/home.html.erb | 4 +- .../errors/internal_server_error.html.erb | 3 + app/views/errors/not_found.html.erb | 3 + app/views/layouts/application.html.erb | 4 +- app/views/uploads/_form.html.erb | 35 ++++---- app/views/uploads/_upload.html.erb | 20 +---- app/views/uploads/new.html.erb | 8 +- app/views/uploads/preview.html.erb | 18 ++++ ...ow.json.jbuilder => preview.json.jbuilder} | 0 app/views/uploads/show.html.erb | 15 ---- config/application.rb | 3 + config/routes.rb | 22 +++-- db/migrate/20231218080634_create_uploads.rb | 9 +- ...te_active_storage_tables.active_storage.rb | 57 +++++++++++++ db/schema.rb | 85 ++++++++++++++----- public/404.html | 67 --------------- public/422.html | 67 --------------- public/500.html | 66 -------------- 27 files changed, 271 insertions(+), 362 deletions(-) delete mode 100644 app/controllers/drops_controller.rb create mode 100644 app/controllers/errors_controller.rb create mode 100644 app/helpers/errors_helper.rb create mode 100644 app/views/errors/internal_server_error.html.erb create mode 100644 app/views/errors/not_found.html.erb create mode 100644 app/views/uploads/preview.html.erb rename app/views/uploads/{show.json.jbuilder => preview.json.jbuilder} (100%) delete mode 100644 app/views/uploads/show.html.erb create mode 100644 db/migrate/20231218090147_create_active_storage_tables.active_storage.rb delete mode 100644 public/404.html delete mode 100644 public/422.html delete mode 100644 public/500.html diff --git a/Gemfile b/Gemfile index 4bcaa5a..a39a03f 100644 --- a/Gemfile +++ b/Gemfile @@ -75,3 +75,5 @@ gem 'inline_svg', '~> 1.9' gem 'annotate', '~> 3.2' gem 'erb-formatter', '~> 0.6.0' + +gem "active_storage_validations", "~> 1.1" diff --git a/Gemfile.lock b/Gemfile.lock index 90a87bc..1a69ddc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -50,6 +50,11 @@ GEM erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) + active_storage_validations (1.1.3) + activejob (>= 5.2.0) + activemodel (>= 5.2.0) + activestorage (>= 5.2.0) + activesupport (>= 5.2.0) activejob (7.1.2) activesupport (= 7.1.2) globalid (>= 0.3.6) @@ -295,6 +300,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + active_storage_validations (~> 1.1) annotate (~> 3.2) bootsnap debug diff --git a/app/controllers/drops_controller.rb b/app/controllers/drops_controller.rb deleted file mode 100644 index 8353b63..0000000 --- a/app/controllers/drops_controller.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -class DropsController < ApplicationController - layout 'drops' - before_action :set_drop, only: %i[show preview] - - def show; end - - def preview; end - - def new - @drop = Drop.new - end - - def create - @drop = Drop.new(drop_params) - - respond_to do |format| - if @drop.save - format.html { redirect_to drop_url(@drop), notice: 'Drop was successfully created.' } - format.json { render :preview, status: :created, location: @drop } - else - format.html { render :new, status: :unprocessable_entity } - format.json { render json: @drop.errors, status: :unprocessable_entity } - end - end - end - - private - - # Use callbacks to share common setup or constraints between actions. - def set_drop - @drop = Drop.find(params[:id]) - end - - # Only allow a list of trusted parameters through. - def drop_params - params.require(:drop).permit(:data, :expiry, :remaining_uses) - end -end diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb new file mode 100644 index 0000000..479fe79 --- /dev/null +++ b/app/controllers/errors_controller.rb @@ -0,0 +1,9 @@ +class ErrorsController < ApplicationController + def not_found + render status: 404 + end + + def internal_server_error + render status: 500 + end +end diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index b360f75..5a476b3 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -1,23 +1,21 @@ # frozen_string_literal: true class UploadsController < ApplicationController - before_action :set_upload, only: %i[show preview] - - def show; end - - def preview; end + include ActiveStorage::SetCurrent + include ActionController::Live def new - @upload = Upload.new + @upload = Upload.new(expiry: 10.minutes.from_now, remaining_uses: 1) end - def create + def upload @upload = Upload.new(upload_params) + @upload.key = Upload.generate_key respond_to do |format| if @upload.save - format.html { redirect_to upload_url(@upload), notice: 'Upload was successfully created.' } - format.json { render :show, status: :created, location: @upload } + format.html { redirect_to preview_url(@upload) } + format.json { render :preview, status: :created, location: @upload } else format.html { render :new, status: :unprocessable_entity } format.json { render json: @upload.errors, status: :unprocessable_entity } @@ -25,15 +23,31 @@ def create end end + def preview + @upload = upload_scope.where(previewed: false).find_by!(key: params[:id]) + @upload.update!(previewed: true) + respond_to do |format| + format.html { render :preview, status: :not_found } + end + end + + def download + @upload = upload_scope.find_by!(key: params[:id]) + @upload.decrement!(:remaining_uses) + + redirect_to rails_blob_path(@upload.data, disposition: 'attachment') + # send_data @upload.data.download, filename: @upload.data.filename.to_s, content_type: @upload.data.content_type + end + private - # Use callbacks to share common setup or constraints between actions. - def set_upload - @upload = Upload.find(params[:id]) + def upload_scope + Upload.where('expiry > ?', DateTime.now).where('remaining_uses > ?', 0) end # Only allow a list of trusted parameters through. def upload_params - params.require(:upload).permit(:path, :data, :expiry, :remaining_uses, :previewed) + params + .require(:upload).permit(:data, :expiry, :remaining_uses) end end diff --git a/app/helpers/errors_helper.rb b/app/helpers/errors_helper.rb new file mode 100644 index 0000000..8e3b415 --- /dev/null +++ b/app/helpers/errors_helper.rb @@ -0,0 +1,2 @@ +module ErrorsHelper +end diff --git a/app/javascript/application.js b/app/javascript/application.js index 0d7b494..d3a0880 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,3 +1,5 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails -import "@hotwired/turbo-rails" -import "controllers" +import "@hotwired/turbo-rails"; +import "controllers"; + +Turbo.setFormMode("optin"); diff --git a/app/models/dice_word.rb b/app/models/dice_word.rb index 8c743d1..4a77823 100644 --- a/app/models/dice_word.rb +++ b/app/models/dice_word.rb @@ -13,15 +13,4 @@ class DiceWord < ApplicationRecord def readonly? true end - - class << self - def generate_password - ids = (1..4).map { (1..5).map { rand(1..6) }.join.to_i } - DiceWord - .find(*ids) - .map(&:words) - .map { |w| w.split(' ') } - .map { |t| t[rand(0..t.length - 1)] }.flatten.join('-') - end - end end diff --git a/app/models/upload.rb b/app/models/upload.rb index 0bf9d48..ed1d953 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -1,5 +1,36 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: uploads +# +# id :integer not null, primary key +# expiry :datetime not null +# key :string not null +# previewed :boolean default(FALSE), not null +# remaining_uses :integer default(1), not null +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_uploads_on_key (key) UNIQUE +# class Upload < ApplicationRecord has_one_attached :data + + validates :data, attached: true, size: { less_than: 512.kilobytes, message: 'is must be smaller than 512 kb' } + + def to_param + key + end + + def self.generate_key + ids = (1..3).map { (1..5).map { rand(1..6) }.join.to_i } + DiceWord + .find(*ids) + .map(&:words) + .map { |w| w.split(' ') } + .map { |t| t[rand(0..t.length - 1)] }.flatten.join('-') + end end diff --git a/app/views/application/home.html.erb b/app/views/application/home.html.erb index 21c5487..375fd26 100644 --- a/app/views/application/home.html.erb +++ b/app/views/application/home.html.erb @@ -19,7 +19,7 @@ block w-full rounded bg-red-600 px-12 py-3 text-sm font-medium text-white shadow hover:bg-red-700 focus:outline-none focus:ring active:bg-red-500 sm:w-auto " - href="<%= new_drop_path %>" + href="<%= new_path %>" > Get Started @@ -53,7 +53,7 @@

Drop some data in our <%= link_to( "Web UI", - new_drop_path, + new_path, class: " text-red-600 no-underline hover:text-red-700 hover:underline active:bg-red-500", ) %> diff --git a/app/views/errors/internal_server_error.html.erb b/app/views/errors/internal_server_error.html.erb new file mode 100644 index 0000000..bc8b90f --- /dev/null +++ b/app/views/errors/internal_server_error.html.erb @@ -0,0 +1,3 @@ +

+

500 | Server Error

+
diff --git a/app/views/errors/not_found.html.erb b/app/views/errors/not_found.html.erb new file mode 100644 index 0000000..6baee1e --- /dev/null +++ b/app/views/errors/not_found.html.erb @@ -0,0 +1,3 @@ +
+

404 | Not Found

+
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index c2f82a5..1fe596b 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -11,7 +11,7 @@ <%= javascript_importmap_tags %> - +
-
+
<%= yield %>
diff --git a/app/views/uploads/_form.html.erb b/app/views/uploads/_form.html.erb index 7c27c5f..264f7bf 100644 --- a/app/views/uploads/_form.html.erb +++ b/app/views/uploads/_form.html.erb @@ -1,7 +1,11 @@ -<%= form_with(model: upload, class: "contents") do |form| %> +<%= form_with(model: upload, class: "contents", url: upload_path) do |form| %> <% if upload.errors.any? %> -
-

<%= pluralize(upload.errors.count, "error") %> prohibited this upload from being saved:

+
+

<%= pluralize(upload.errors.count, "error") %> + prohibited this upload from being saved:

    <% upload.errors.each do |error| %> @@ -11,32 +15,29 @@
<% end %> -
- <%= form.label :path %> - <%= form.text_field :path, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %> -
-
<%= form.label :data %> - <%= form.file_field :data, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %> + <%= form.file_field :data, + class: + "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
<%= form.label :expiry %> - <%= form.datetime_field :expiry, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %> + <%= form.datetime_field :expiry, + class: + "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
<%= form.label :remaining_uses %> - <%= form.number_field :remaining_uses, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %> -
- -
- <%= form.label :previewed %> - <%= form.check_box :previewed, class: "block mt-2 h-5 w-5" %> + <%= form.number_field :remaining_uses, + class: + "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
- <%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %> + <%= form.submit class: + "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
<% end %> diff --git a/app/views/uploads/_upload.html.erb b/app/views/uploads/_upload.html.erb index 8a02462..d49a117 100644 --- a/app/views/uploads/_upload.html.erb +++ b/app/views/uploads/_upload.html.erb @@ -1,12 +1,7 @@

- Path: - <%= upload.path %> -

- -

- Data: - <%= link_to upload.data.filename, upload.data if upload.data.attached? %> + Key: + <%= upload.key %>

@@ -18,15 +13,4 @@ Remaining uses: <%= upload.remaining_uses %>

- -

- Previewed: - <%= upload.previewed %> -

- - <% if action_name != "show" %> - <%= link_to "Show this upload", upload, class: "rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %> - <%= link_to "Edit this upload", edit_upload_path(upload), class: "rounded-lg py-3 ml-2 px-5 bg-gray-100 inline-block font-medium" %> -
- <% end %>
diff --git a/app/views/uploads/new.html.erb b/app/views/uploads/new.html.erb index 478fdfa..1786894 100644 --- a/app/views/uploads/new.html.erb +++ b/app/views/uploads/new.html.erb @@ -1,11 +1,7 @@ -
-
+
+

New upload

<%= render "form", upload: @upload %> - - <%= link_to "Back to uploads", - uploads_path, - class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
diff --git a/app/views/uploads/preview.html.erb b/app/views/uploads/preview.html.erb new file mode 100644 index 0000000..cf8f9a5 --- /dev/null +++ b/app/views/uploads/preview.html.erb @@ -0,0 +1,18 @@ +
+

A Preview

+
+
+ <% if notice.present? %> +

<%= notice %>

+ <% end %> + + <%= render @upload %> + +
+
+
diff --git a/app/views/uploads/show.json.jbuilder b/app/views/uploads/preview.json.jbuilder similarity index 100% rename from app/views/uploads/show.json.jbuilder rename to app/views/uploads/preview.json.jbuilder diff --git a/app/views/uploads/show.html.erb b/app/views/uploads/show.html.erb deleted file mode 100644 index feed402..0000000 --- a/app/views/uploads/show.html.erb +++ /dev/null @@ -1,15 +0,0 @@ -
-
- <% if notice.present? %> -

<%= notice %>

- <% end %> - - <%= render @upload %> - - <%= link_to "Edit this upload", edit_upload_path(@upload), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %> -
- <%= button_to "Destroy this upload", upload_path(@upload), method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %> -
- <%= link_to "Back to uploads", uploads_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %> -
-
diff --git a/config/application.rb b/config/application.rb index 2d330b4..798c662 100644 --- a/config/application.rb +++ b/config/application.rb @@ -40,5 +40,8 @@ class Application < Rails::Application # Don't generate system test files. config.generators.system_tests = nil + + # Use routes to render error pages + config.exceptions_app = routes end end diff --git a/config/routes.rb b/config/routes.rb index b0342bf..4e255c5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,16 +1,20 @@ # frozen_string_literal: true Rails.application.routes.draw do - resources :uploads, only: %i[new create show] do - member do - get 'preview' - end - end + get 'errors/not_found' + get 'errors/internal_server_error' root 'application#home' - # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. - # Can be used by load balancers and uptime monitors to verify that the app is live. + + get '/new', to: 'uploads#new', as: :new + post '/upload', to: 'uploads#upload', as: :upload + get '/download/:id/preview', to: 'uploads#preview', as: :preview + get '/download/:id', to: 'uploads#download', as: :download get 'up' => 'rails/health#show', as: :rails_health_check - # Defines the root path route ("/") - # root "posts#index" + # Preview error pages + get '/errors/404', to: 'errors#not_found' + get '/errors/500', to: 'errors#internal_server_error' + + match '/404', to: 'errors#not_found', via: :all + match '/500', to: 'errors#internal_server_error', via: :all end diff --git a/db/migrate/20231218080634_create_uploads.rb b/db/migrate/20231218080634_create_uploads.rb index 3af6f39..6400c22 100644 --- a/db/migrate/20231218080634_create_uploads.rb +++ b/db/migrate/20231218080634_create_uploads.rb @@ -3,12 +3,13 @@ class CreateUploads < ActiveRecord::Migration[7.1] def change create_table :uploads do |t| - t.string :path - t.datetime :expiry - t.integer :remaining_uses - t.boolean :previewed + t.string :key, null: false + t.datetime :expiry, null: false + t.integer :remaining_uses, null: false, default: 1 + t.boolean :previewed, null: false, default: 0 t.timestamps + t.index :key, unique: true end end end diff --git a/db/migrate/20231218090147_create_active_storage_tables.active_storage.rb b/db/migrate/20231218090147_create_active_storage_tables.active_storage.rb new file mode 100644 index 0000000..e4706aa --- /dev/null +++ b/db/migrate/20231218090147_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/schema.rb b/db/schema.rb index 8941ad1..37c60aa 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - # 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. @@ -12,32 +10,73 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 20_231_212_173_231) do - create_table 'dead_drops', force: :cascade do |t| - t.string 'url' - t.datetime 'expiry' - t.integer 'uses' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false +ActiveRecord::Schema[7.1].define(version: 2023_12_18_090147) 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 'dice_words', force: :cascade do |t| - t.string 'words' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false + create_table "dead_drops", force: :cascade do |t| + t.string "url" + t.datetime "expiry" + t.integer "uses" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - create_table 'dicewords', force: :cascade do |t| - t.string 'words' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false + create_table "dice_words", force: :cascade do |t| + t.string "words" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - create_table 'drops', force: :cascade do |t| - t.string 'path' - t.datetime 'expiry' - t.integer 'remaining_uses' - t.datetime 'created_at', null: false - t.datetime 'updated_at', null: false + create_table "dicewords", force: :cascade do |t| + t.string "words" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end + + create_table "drops", force: :cascade do |t| + t.string "path" + t.datetime "expiry" + t.integer "remaining_uses" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "uploads", force: :cascade do |t| + t.string "key", null: false + t.datetime "expiry", null: false + t.integer "remaining_uses", default: 1, null: false + t.boolean "previewed", default: false, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["key"], name: "index_uploads_on_key", unique: true + 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" end diff --git a/public/404.html b/public/404.html deleted file mode 100644 index 2be3af2..0000000 --- a/public/404.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - The page you were looking for doesn't exist (404) - - - - - - -
-
-

The page you were looking for doesn't exist.

-

You may have mistyped the address or the page may have moved.

-
-

If you are the application owner check the logs for more information.

-
- - diff --git a/public/422.html b/public/422.html deleted file mode 100644 index c08eac0..0000000 --- a/public/422.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - The change you wanted was rejected (422) - - - - - - -
-
-

The change you wanted was rejected.

-

Maybe you tried to change something you didn't have access to.

-
-

If you are the application owner check the logs for more information.

-
- - diff --git a/public/500.html b/public/500.html deleted file mode 100644 index 78a030a..0000000 --- a/public/500.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - We're sorry, but something went wrong (500) - - - - - - -
-
-

We're sorry, but something went wrong.

-
-

If you are the application owner check the logs for more information.

-
- -