diff --git a/.gitignore b/.gitignore index e16dc71..8a0a6a0 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ # Ignore master key for decrypting credentials and more. /config/master.key + +# Ignore Rubymine files +.idea/ diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..c99d2e7 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..3cc00bb --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,34 @@ +require: + - rubocop-rails + +AllCops: + TargetRubyVersion: 3.1.2 + Exclude: + - 'app/admin/**/*' + - 'bin/**/*' + - 'vendor/**/*' + - 'db/**/*' + - 'lib/tasks/**/*' + - 'config/**/*' + - 'spec/rails_helper.rb' + - 'spec/spec_helper.rb' +Documentation: + Enabled: false + +Style/FrozenStringLiteralComment: + Enabled: false + +Lint/MissingSuper: + Enabled: false + +Metrics/BlockLength: + ExcludedMethods: ['describe', 'context', 'feature', 'scenario', 'let', 'register', 'path'] + +Metrics/LineLength: + Max: 120 + +Metrics/MethodLength: + Max: 20 + +AsciiComments: + Enabled: false \ No newline at end of file diff --git a/Gemfile b/Gemfile index e2207f7..75e659f 100644 --- a/Gemfile +++ b/Gemfile @@ -1,72 +1,43 @@ -source "https://rubygems.org" +source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby "3.1.2" - -# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" -gem "rails", "~> 7.0.8" - -# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] -gem "sprockets-rails" - -# Use postgresql as the database for Active Record -gem "pg", "~> 1.1" - -# Use the Puma web server [https://github.com/puma/puma] -gem "puma", "~> 5.0" - -# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] -gem "importmap-rails" - -# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev] -gem "turbo-rails" - -# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev] -gem "stimulus-rails" - -# Build JSON APIs with ease [https://github.com/rails/jbuilder] -gem "jbuilder" - -# Use Redis adapter to run Action Cable in production -gem "redis", "~> 4.0" - -# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] -# gem "kredis" - -# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] -# gem "bcrypt", "~> 3.1.7" - -# Windows does not include zoneinfo files, so bundle the tzinfo-data gem -gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] - -# Reduces boot times through caching; required in config/boot.rb -gem "bootsnap", require: false - -# Use Sass to process CSS -# gem "sassc-rails" - -# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] -# gem "image_processing", "~> 1.2" +ruby '3.1.2' + +gem 'bootsnap', require: false +gem 'foreman', '~> 0.87.2' +gem 'importmap-rails' +gem 'jbuilder' +gem 'pg', '~> 1.1' +gem 'puma', '~> 5.0' +gem 'rails', '~> 7.0.8' +gem 'redis', '~> 4.0' +gem 'sprockets-rails' +gem 'stimulus-rails' +gem 'turbo-rails' +gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] group :development, :test do - # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem - gem "debug", platforms: %i[ mri mingw x64_mingw ] + gem 'database_cleaner', '~> 2.0', '>= 2.0.2' + gem 'debug', platforms: %i[mri mingw x64_mingw] + gem 'factory_bot', '~> 6.4', '>= 6.4.5' + gem 'factory_bot_rails', '~> 6.2' + gem 'rubocop', require: false + gem 'rubocop-capybara', '~> 2.19' + gem 'rubocop-rails', require: false + gem 'rubocop-rspec', '~> 2.25' end group :development do - # Use console on exceptions pages [https://github.com/rails/web-console] - gem "web-console" - - # Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler] - # gem "rack-mini-profiler" - - # Speed up commands on slow machines / big apps [https://github.com/rails/spring] - # gem "spring" + gem 'web-console' end group :test do - # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] - gem "capybara" - gem "selenium-webdriver" - + gem 'capybara' + gem 'ffaker', '~> 2.21' + gem 'rails-controller-testing' + gem 'rspec-rails' + gem 'selenium-webdriver' + gem 'shoulda-matchers', '~> 6.0' end + +gem 'tailwindcss-rails', '~> 2.3' diff --git a/Gemfile.lock b/Gemfile.lock index 823c3b2..74573c0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -68,6 +68,7 @@ GEM tzinfo (~> 2.0) addressable (2.8.6) public_suffix (>= 2.0.2, < 6.0) + ast (2.4.2) bindex (0.8.1) bootsnap (1.17.1) msgpack (~> 1.2) @@ -83,11 +84,25 @@ GEM xpath (~> 3.2) concurrent-ruby (1.2.3) crass (1.0.6) + database_cleaner (2.0.2) + database_cleaner-active_record (>= 2, < 3) + database_cleaner-active_record (2.1.0) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0.0) + database_cleaner-core (2.0.1) date (3.3.4) debug (1.9.1) irb (~> 1.10) reline (>= 0.3.8) + diff-lcs (1.5.0) erubi (1.12.0) + factory_bot (6.4.5) + activesupport (>= 5.0.0) + factory_bot_rails (6.4.3) + factory_bot (~> 6.4) + railties (>= 5.0.0) + ffaker (2.23.0) + foreman (0.87.2) globalid (1.2.1) activesupport (>= 6.1) i18n (1.14.1) @@ -103,6 +118,8 @@ GEM jbuilder (2.11.5) actionview (>= 5.0.0) activesupport (>= 5.0.0) + json (2.7.1) + language_server-protocol (3.17.0.3) loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -129,6 +146,10 @@ GEM nio4r (2.7.0) nokogiri (1.16.0-x86_64-linux) racc (~> 1.4) + parallel (1.24.0) + parser (3.3.0.3) + ast (~> 2.4.1) + racc pg (1.5.4) psych (5.1.2) stringio @@ -153,6 +174,10 @@ GEM activesupport (= 7.0.8) bundler (>= 1.15.0) railties (= 7.0.8) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -167,6 +192,7 @@ GEM rake (>= 12.2) thor (~> 1.0) zeitwerk (~> 2.5) + rainbow (3.1.1) rake (13.1.0) rdoc (6.6.2) psych (>= 4.0.0) @@ -175,11 +201,57 @@ GEM reline (0.4.2) io-console (~> 0.5) rexml (3.2.6) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.6) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-rails (6.1.0) + actionpack (>= 6.1) + activesupport (>= 6.1) + railties (>= 6.1) + rspec-core (~> 3.12) + rspec-expectations (~> 3.12) + rspec-mocks (~> 3.12) + rspec-support (~> 3.12) + rspec-support (3.12.1) + rubocop (1.59.0) + json (~> 2.3) + language_server-protocol (>= 3.17.0) + parallel (~> 1.10) + parser (>= 3.2.2.4) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.30.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.30.0) + parser (>= 3.2.1.0) + rubocop-capybara (2.20.0) + rubocop (~> 1.41) + rubocop-factory_bot (2.25.1) + rubocop (~> 1.41) + rubocop-rails (2.23.1) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.33.0, < 2.0) + rubocop-ast (>= 1.30.0, < 2.0) + rubocop-rspec (2.26.1) + rubocop (~> 1.40) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) + ruby-progressbar (1.13.0) rubyzip (2.3.2) selenium-webdriver (4.16.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) + shoulda-matchers (6.0.0) + activesupport (>= 5.2.0) sprockets (4.2.1) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) @@ -190,6 +262,8 @@ GEM stimulus-rails (1.3.3) railties (>= 6.0.0) stringio (3.1.0) + tailwindcss-rails (2.3.0-x86_64-linux) + railties (>= 6.0.0) thor (1.3.0) timeout (0.4.1) turbo-rails (1.5.0) @@ -198,6 +272,7 @@ GEM railties (>= 6.0.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) + unicode-display_width (2.5.0) web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) @@ -217,16 +292,29 @@ PLATFORMS DEPENDENCIES bootsnap capybara + database_cleaner (~> 2.0, >= 2.0.2) debug + factory_bot (~> 6.4, >= 6.4.5) + factory_bot_rails (~> 6.2) + ffaker (~> 2.21) + foreman (~> 0.87.2) importmap-rails jbuilder pg (~> 1.1) puma (~> 5.0) rails (~> 7.0.8) + rails-controller-testing redis (~> 4.0) + rspec-rails + rubocop + rubocop-capybara (~> 2.19) + rubocop-rails + rubocop-rspec (~> 2.25) selenium-webdriver + shoulda-matchers (~> 6.0) sprockets-rails stimulus-rails + tailwindcss-rails (~> 2.3) turbo-rails tzinfo-data web-console diff --git a/Procfile.dev b/Procfile.dev new file mode 100644 index 0000000..023e98a --- /dev/null +++ b/Procfile.dev @@ -0,0 +1,2 @@ +web: bin/rails server -p 3000 +css: bin/rails tailwindcss:watch diff --git a/Rakefile b/Rakefile index 9a5ea73..e85f913 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require_relative "config/application" +require_relative 'config/application' Rails.application.load_tasks diff --git a/app/assets/builds/tailwind.css b/app/assets/builds/tailwind.css new file mode 100644 index 0000000..199c36f --- /dev/null +++ b/app/assets/builds/tailwind.css @@ -0,0 +1 @@ +/*! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:Inter var,ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],select,textarea{--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-radius:0;border-width:1px;font-size:1rem;line-height:1.5rem;padding:.5rem .75rem}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,select:focus,textarea:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);border-color:#2563eb;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid #0000;outline-offset:2px}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-bottom:0;padding-top:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple]{background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;background-origin:border-box;border-color:#6b7280;border-width:1px;color:#2563eb;display:inline-block;flex-shrink:0;height:1rem;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;width:1rem}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid #0000;outline-offset:2px}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=checkbox]:indeterminate,[type=radio]:checked:focus,[type=radio]:checked:hover{background-color:currentColor;border-color:#0000}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:100% 100%}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{background-color:currentColor;border-color:#0000}[type=file]{background:unset;border-color:inherit;border-radius:0;border-width:0;font-size:unset;line-height:inherit;padding:0}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.mx-auto{margin-left:auto;margin-right:auto}.mt-28{margin-top:7rem}.block{display:block}.inline{display:inline}.flex{display:flex}.table{display:table}.min-w-full{min-width:100%}.flex-col{flex-direction:column}.overflow-x-auto{overflow-x:auto}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.hover\:underline:hover{text-decoration-line:underline} \ No newline at end of file diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index ddd546a..b06fc42 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -2,3 +2,4 @@ //= link_directory ../stylesheets .css //= link_tree ../../javascript .js //= link_tree ../../../vendor/javascript .js +//= link_tree ../builds diff --git a/app/assets/images/.keep b/app/assets/images/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css new file mode 100644 index 0000000..8666d2f --- /dev/null +++ b/app/assets/stylesheets/application.tailwind.css @@ -0,0 +1,13 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* + +@layer components { + .btn-primary { + @apply py-2 px-4 bg-blue-200; + } +} + +*/ diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/app/controllers/municipes_controller.rb b/app/controllers/municipes_controller.rb new file mode 100644 index 0000000..d1a1d8f --- /dev/null +++ b/app/controllers/municipes_controller.rb @@ -0,0 +1,44 @@ +class MunicipesController < ApplicationController + before_action :set_municipe, only: %i[show edit update] + + def index + @municipes = Municipe.all + end + + def show; end + + def new + @municipe = Municipe.new + end + + def create + @municipe = Municipe.new(municipe_params) + + if @municipe.save + redirect_to @municipe, notice: t('.create') + else + render :new, status: :unprocessable_entity + end + end + + def edit; end + + def update + if @municipe.update(municipe_params) + redirect_to @municipe, notice: t('.update') + else + render :edit, status: :unprocessable_entity + end + end + + private + + def set_municipe + @municipe = Municipe.find(params[:id]) + end + + def municipe_params + params.require(:municipe).permit(:full_name, :cpf, :cns, :email, :email_confirmation, :birth_date, :phone_number, + :photo, :status) + end +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 3c34c81..286b223 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,4 +1,4 @@ class ApplicationMailer < ActionMailer::Base - default from: "from@example.com" - layout "mailer" + default from: 'from@example.com' + layout 'mailer' end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/app/models/municipe.rb b/app/models/municipe.rb new file mode 100644 index 0000000..def7a8c --- /dev/null +++ b/app/models/municipe.rb @@ -0,0 +1,13 @@ +class Municipe < ApplicationRecord + enum status: { active: 0, inactive: 1 } + has_one_attached :photo + + validates :cpf, cpf: true + validates :cns, cns: true + validates :birth_date, birth_date: true + validates :email, confirmation: true, format: { with: URI::MailTo::EMAIL_REGEXP } + validates :email, confirmation: { case_sensitive: false } + + validates :full_name, :cpf, :cns, :email, :email_confirmation, :birth_date, :phone_number, :photo, :status, + presence: true +end diff --git a/app/validators/birth_date_validator.rb b/app/validators/birth_date_validator.rb new file mode 100644 index 0000000..8a8fca4 --- /dev/null +++ b/app/validators/birth_date_validator.rb @@ -0,0 +1,15 @@ +class BirthDateValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + return if birth_date_valid?(value) + + record.errors.add(attribute, options[:message] || :invalid_birth_date) + end + + private + + def birth_date_valid?(birth_date) + return false if birth_date.nil? || birth_date > Date.current || Date.current.year - birth_date.year > 116 + + true + end +end diff --git a/app/validators/cns_validator.rb b/app/validators/cns_validator.rb new file mode 100644 index 0000000..df672bd --- /dev/null +++ b/app/validators/cns_validator.rb @@ -0,0 +1,64 @@ +class CnsValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + return if cns_valid?(value) || prov_cns_valid?(value) + + record.errors.add(attribute, options[:message] || :invalid_cns) + end + + private + + def cns_valid?(cns) + return false unless cns && cns.strip.length == 15 + + pis = cns[0, 11] + sum = calculate_sum(pis) + + dv = calculate_dv(sum) + + result = if dv == 10 + calculate_result(pis, 2) + else + "#{pis}000#{dv.to_i}" + end + + cns == result + end + + def calculate_sum(pis) + sum = 0 + pis.each_char.with_index do |digit, index| + weight = 15 - index + sum += digit.to_i * weight + end + sum + end + + def calculate_dv(sum) + rest = sum % 11 + dv = 11 - rest + dv.zero? ? 0 : dv + end + + def calculate_result(pis, extra_sum) + sum = calculate_sum(pis) + sum += extra_sum + rest = sum % 11 + dv = 11 - rest + "#{pis}001#{dv.to_i}" + end + + def prov_cns_valid?(cns) + return false if cns.nil? || cns.strip.length != 15 + + sum = 0 + + cns.each_char.with_index do |digit, index| + weight = 15 - index + sum += digit.to_i * weight + end + + rest = sum % 11 + + rest.zero? + end +end diff --git a/app/validators/cpf_validator.rb b/app/validators/cpf_validator.rb new file mode 100644 index 0000000..d4b1e10 --- /dev/null +++ b/app/validators/cpf_validator.rb @@ -0,0 +1,28 @@ +class CpfValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + return if cpf_valid?(value) + + record.errors.add(attribute, options[:message] || :invalid_cpf) + end + + private + + def cpf_valid?(cpf) + return false if cpf.nil? + + digits = cpf.scan(/[0-9]/).map(&:to_i) + + return false unless digits.length == 11 + + result1 = calculate_digit(digits, 9) + result2 = calculate_digit(digits, 10) + + result1 == digits[9] && result2 == digits[10] + end + + def calculate_digit(digits, position) + sum = digits.take(position).reverse_each.with_index(2).sum { |digit, i| digit * i } + sum -= (11 * (sum / 11)) + [0, 1].include?(sum) ? 0 : 11 - sum + end +end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 3bab1c7..e50b407 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -5,12 +5,15 @@ <%= csrf_meta_tags %> <%= csp_meta_tag %> + <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %> <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> <%= javascript_importmap_tags %>
- <%= yield %> +