diff --git a/Gemfile b/Gemfile
index c0bd89b..d2e5632 100644
--- a/Gemfile
+++ b/Gemfile
@@ -65,7 +65,7 @@ gem "image_processing", "~> 1.13"
# gem "ruby-vips", "~> 2.2"
gem "mini_magick", "~> 4.13.2"
-gem 'devise', '~> 4.9'
+gem "devise", "~> 4.9"
group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
diff --git a/app/models/user.rb b/app/models/user.rb
new file mode 100644
index 0000000..4756799
--- /dev/null
+++ b/app/models/user.rb
@@ -0,0 +1,6 @@
+class User < ApplicationRecord
+ # Include default devise modules. Others available are:
+ # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
+ devise :database_authenticatable, :registerable,
+ :recoverable, :rememberable, :validatable
+end
diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb
new file mode 100644
index 0000000..b12dd0c
--- /dev/null
+++ b/app/views/devise/confirmations/new.html.erb
@@ -0,0 +1,16 @@
+
Resend confirmation instructions
+
+<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
+ <%= render "devise/shared/error_messages", resource: resource %>
+
+
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
+
+
+
+ <%= f.submit "Resend confirmation instructions" %>
+
+<% end %>
+
+<%= render "devise/shared/links" %>
diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb
new file mode 100644
index 0000000..dc55f64
--- /dev/null
+++ b/app/views/devise/mailer/confirmation_instructions.html.erb
@@ -0,0 +1,5 @@
+Welcome <%= @email %>!
+
+You can confirm your account email through the link below:
+
+<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
diff --git a/app/views/devise/mailer/email_changed.html.erb b/app/views/devise/mailer/email_changed.html.erb
new file mode 100644
index 0000000..32f4ba8
--- /dev/null
+++ b/app/views/devise/mailer/email_changed.html.erb
@@ -0,0 +1,7 @@
+Hello <%= @email %>!
+
+<% if @resource.try(:unconfirmed_email?) %>
+ We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.
+<% else %>
+ We're contacting you to notify you that your email has been changed to <%= @resource.email %>.
+<% end %>
diff --git a/app/views/devise/mailer/password_change.html.erb b/app/views/devise/mailer/password_change.html.erb
new file mode 100644
index 0000000..b41daf4
--- /dev/null
+++ b/app/views/devise/mailer/password_change.html.erb
@@ -0,0 +1,3 @@
+Hello <%= @resource.email %>!
+
+We're contacting you to notify you that your password has been changed.
diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb
new file mode 100644
index 0000000..f667dc1
--- /dev/null
+++ b/app/views/devise/mailer/reset_password_instructions.html.erb
@@ -0,0 +1,8 @@
+Hello <%= @resource.email %>!
+
+Someone has requested a link to change your password. You can do this through the link below.
+
+<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>
+
+If you didn't request this, please ignore this email.
+Your password won't change until you access the link above and create a new one.
diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb
new file mode 100644
index 0000000..41e148b
--- /dev/null
+++ b/app/views/devise/mailer/unlock_instructions.html.erb
@@ -0,0 +1,7 @@
+Hello <%= @resource.email %>!
+
+Your account has been locked due to an excessive number of unsuccessful sign in attempts.
+
+Click the link below to unlock your account:
+
+<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>
diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb
new file mode 100644
index 0000000..5fbb9ff
--- /dev/null
+++ b/app/views/devise/passwords/edit.html.erb
@@ -0,0 +1,25 @@
+Change your password
+
+<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
+ <%= render "devise/shared/error_messages", resource: resource %>
+ <%= f.hidden_field :reset_password_token %>
+
+
+ <%= f.label :password, "New password" %>
+ <% if @minimum_password_length %>
+ (<%= @minimum_password_length %> characters minimum)
+ <% end %>
+ <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
+
+
+
+ <%= f.label :password_confirmation, "Confirm new password" %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
+
+
+
+ <%= f.submit "Change my password" %>
+
+<% end %>
+
+<%= render "devise/shared/links" %>
diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb
new file mode 100644
index 0000000..9b486b8
--- /dev/null
+++ b/app/views/devise/passwords/new.html.erb
@@ -0,0 +1,16 @@
+Forgot your password?
+
+<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
+ <%= render "devise/shared/error_messages", resource: resource %>
+
+
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
+
+
+
+ <%= f.submit "Send me reset password instructions" %>
+
+<% end %>
+
+<%= render "devise/shared/links" %>
diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb
new file mode 100644
index 0000000..b82e336
--- /dev/null
+++ b/app/views/devise/registrations/edit.html.erb
@@ -0,0 +1,43 @@
+Edit <%= resource_name.to_s.humanize %>
+
+<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
+ <%= render "devise/shared/error_messages", resource: resource %>
+
+
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
+
+
+ <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
+ Currently waiting confirmation for: <%= resource.unconfirmed_email %>
+ <% end %>
+
+
+ <%= f.label :password %> (leave blank if you don't want to change it)
+ <%= f.password_field :password, autocomplete: "new-password" %>
+ <% if @minimum_password_length %>
+
+ <%= @minimum_password_length %> characters minimum
+ <% end %>
+
+
+
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
+
+
+
+ <%= f.label :current_password %> (we need your current password to confirm your changes)
+ <%= f.password_field :current_password, autocomplete: "current-password" %>
+
+
+
+ <%= f.submit "Update" %>
+
+<% end %>
+
+Cancel my account
+
+Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %>
+
+<%= link_to "Back", :back %>
diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb
new file mode 100644
index 0000000..d655b66
--- /dev/null
+++ b/app/views/devise/registrations/new.html.erb
@@ -0,0 +1,29 @@
+Sign up
+
+<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
+ <%= render "devise/shared/error_messages", resource: resource %>
+
+
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
+
+
+
+ <%= f.label :password %>
+ <% if @minimum_password_length %>
+ (<%= @minimum_password_length %> characters minimum)
+ <% end %>
+ <%= f.password_field :password, autocomplete: "new-password" %>
+
+
+
+ <%= f.label :password_confirmation %>
+ <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
+
+
+
+ <%= f.submit "Sign up" %>
+
+<% end %>
+
+<%= render "devise/shared/links" %>
diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb
new file mode 100644
index 0000000..5ede964
--- /dev/null
+++ b/app/views/devise/sessions/new.html.erb
@@ -0,0 +1,26 @@
+Log in
+
+<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
+
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
+
+
+
+ <%= f.label :password %>
+ <%= f.password_field :password, autocomplete: "current-password" %>
+
+
+ <% if devise_mapping.rememberable? %>
+
+ <%= f.check_box :remember_me %>
+ <%= f.label :remember_me %>
+
+ <% end %>
+
+
+ <%= f.submit "Log in" %>
+
+<% end %>
+
+<%= render "devise/shared/links" %>
diff --git a/app/views/devise/shared/_error_messages.html.erb b/app/views/devise/shared/_error_messages.html.erb
new file mode 100644
index 0000000..cabfe30
--- /dev/null
+++ b/app/views/devise/shared/_error_messages.html.erb
@@ -0,0 +1,15 @@
+<% if resource.errors.any? %>
+
+
+ <%= I18n.t("errors.messages.not_saved",
+ count: resource.errors.count,
+ resource: resource.class.model_name.human.downcase)
+ %>
+
+
+ <% resource.errors.full_messages.each do |message| %>
+ - <%= message %>
+ <% end %>
+
+
+<% end %>
diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb
new file mode 100644
index 0000000..7a75304
--- /dev/null
+++ b/app/views/devise/shared/_links.html.erb
@@ -0,0 +1,25 @@
+<%- if controller_name != 'sessions' %>
+ <%= link_to "Log in", new_session_path(resource_name) %>
+<% end %>
+
+<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
+ <%= link_to "Sign up", new_registration_path(resource_name) %>
+<% end %>
+
+<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
+ <%= link_to "Forgot your password?", new_password_path(resource_name) %>
+<% end %>
+
+<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
+ <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
+<% end %>
+
+<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
+ <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
+<% end %>
+
+<%- if devise_mapping.omniauthable? %>
+ <%- resource_class.omniauth_providers.each do |provider| %>
+ <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %>
+ <% end %>
+<% end %>
diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb
new file mode 100644
index 0000000..ffc34de
--- /dev/null
+++ b/app/views/devise/unlocks/new.html.erb
@@ -0,0 +1,16 @@
+Resend unlock instructions
+
+<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
+ <%= render "devise/shared/error_messages", resource: resource %>
+
+
+ <%= f.label :email %>
+ <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
+
+
+
+ <%= f.submit "Resend unlock instructions" %>
+
+<% end %>
+
+<%= render "devise/shared/links" %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index bf4b014..83a7950 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -69,7 +69,27 @@
-
+
+
+ <% if notice %>
+
+ <% end %>
+
+ <% if alert %>
+
+ <% end %>
+
+
<%= yield %>
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index a79af56..e150d16 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -14,7 +14,7 @@ Devise.setup do |config|
# confirmation, reset password and unlock tokens in the database.
# Devise will use the `secret_key_base` as its `secret_key`
# by default. You can change it below and use your own secret key.
- # config.secret_key = '8e01592add1deb098d03551e0d56b27cf262887b42ba72306fb604d7fb02a96cd2b9708262fffe4df924c3a8feb480e79a37709685346aadfc3e56efb4c49968'
+ # config.secret_key = '81c9caf47bc0267e289d8584e632053eb7de20e1cc1f2d64ce5ba081229caf1117826d4dac2c5ca60471871dc54fbd731f57f2b843246c9fbc83ed3e427b3070'
# ==> Controller configuration
# Configure the parent class to the devise controllers.
@@ -126,7 +126,7 @@ Devise.setup do |config|
config.stretches = Rails.env.test? ? 1 : 12
# Set up a pepper to generate the hashed password.
- # config.pepper = '8c7d54d89c6dcb3a9835bd41bcd8b907633b8c307849b255af68aefa4815838938c646722094a0b9b5c472417854bca020a84e291c4e00c1d404ce48eaf5e4e0'
+ # config.pepper = 'b06ea5045bb70198284af2b53bb9421b1ba33ba75f57972960c79e50f1d8018b36a2d11c59a9da2b135a7775077da7ce29f23099ded2070c3c7af505253547ab'
# Send a notification to the original email when the user's email is changed.
# config.send_email_changed_notification = false
diff --git a/config/routes.rb b/config/routes.rb
index afea55e..b406b01 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,6 +1,7 @@
require "sidekiq/web"
Rails.application.routes.draw do
+ devise_for :users
root "home#index"
resources :cities, only: [ :index, :show ] do
diff --git a/db/migrate/20250211035423_devise_create_users.rb b/db/migrate/20250211035423_devise_create_users.rb
new file mode 100644
index 0000000..cb28f9b
--- /dev/null
+++ b/db/migrate/20250211035423_devise_create_users.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+class DeviseCreateUsers < ActiveRecord::Migration[8.0]
+ def change
+ create_table :users do |t|
+ ## Database authenticatable
+ t.string :email, null: false, default: ""
+ t.string :encrypted_password, null: false, default: ""
+
+ ## Recoverable
+ t.string :reset_password_token
+ t.datetime :reset_password_sent_at
+
+ ## Rememberable
+ t.datetime :remember_created_at
+
+ ## Trackable
+ t.integer :sign_in_count, default: 0, null: false
+ t.datetime :current_sign_in_at
+ t.datetime :last_sign_in_at
+ t.string :current_sign_in_ip
+ t.string :last_sign_in_ip
+
+ ## Confirmable
+ t.string :confirmation_token
+ t.datetime :confirmed_at
+ t.datetime :confirmation_sent_at
+ t.string :unconfirmed_email # Only if using reconfirmable
+
+ ## Lockable
+ t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
+ t.string :unlock_token # Only if unlock strategy is :email or :both
+ t.datetime :locked_at
+
+ ## Admin
+ t.boolean :admin, default: false
+
+
+ t.timestamps null: false
+ end
+
+ add_index :users, :email, unique: true
+ add_index :users, :reset_password_token, unique: true
+ # add_index :users, :confirmation_token, unique: true
+ # add_index :users, :unlock_token, unique: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0a3cab8..642f5b6 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[8.0].define(version: 2025_02_08_052634) do
+ActiveRecord::Schema[8.0].define(version: 2025_02_11_035423) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
@@ -125,7 +125,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_02_08_052634) do
t.string "country_code"
t.boolean "flag", default: true
t.string "wiki_data_id"
- t.integer "state_id"
+ t.bigint "state_id"
t.index ["country_id"], name: "index_cities_on_country_id"
t.index ["slug"], name: "index_cities_on_slug", unique: true
t.index ["state_id"], name: "index_cities_on_state_id"
@@ -158,7 +158,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_02_08_052634) do
t.string "emoji_u"
t.boolean "flag", default: true
t.string "wiki_data_id"
- t.integer "subregion_id"
+ t.bigint "subregion_id"
t.index ["code"], name: "index_countries_on_code", unique: true
t.index ["region_id"], name: "index_countries_on_region_id"
t.index ["slug"], name: "index_countries_on_slug", unique: true
@@ -192,7 +192,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_02_08_052634) do
create_table "states", force: :cascade do |t|
t.string "name"
t.string "code"
- t.integer "country_id"
+ t.bigint "country_id"
t.string "country_code"
t.string "fips_code"
t.string "iso2"
@@ -211,7 +211,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_02_08_052634) do
create_table "subregions", force: :cascade do |t|
t.string "name", null: false
t.text "translations"
- t.integer "region_id", null: false
+ t.bigint "region_id", null: false
t.boolean "flag", default: true
t.string "wiki_data_id"
t.datetime "created_at", null: false
@@ -219,6 +219,31 @@ ActiveRecord::Schema[8.0].define(version: 2025_02_08_052634) do
t.index ["region_id"], name: "index_subregions_on_region_id"
end
+ create_table "users", force: :cascade do |t|
+ t.string "email", default: "", null: false
+ t.string "encrypted_password", default: "", null: false
+ t.string "reset_password_token"
+ t.datetime "reset_password_sent_at"
+ t.datetime "remember_created_at"
+ t.integer "sign_in_count", default: 0, null: false
+ t.datetime "current_sign_in_at"
+ t.datetime "last_sign_in_at"
+ t.string "current_sign_in_ip"
+ t.string "last_sign_in_ip"
+ t.string "confirmation_token"
+ t.datetime "confirmed_at"
+ t.datetime "confirmation_sent_at"
+ t.string "unconfirmed_email"
+ t.integer "failed_attempts", default: 0, null: false
+ t.string "unlock_token"
+ t.datetime "locked_at"
+ t.boolean "admin", default: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["email"], name: "index_users_on_email", unique: true
+ t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
+ end
+
create_table "weather_arts", force: :cascade do |t|
t.integer "city_id", null: false
t.date "weather_date"
diff --git a/lib/tasks/sync_geo_data.rake b/lib/tasks/sync_geo_data.rake
index 7df77df..1db829d 100644
--- a/lib/tasks/sync_geo_data.rake
+++ b/lib/tasks/sync_geo_data.rake
@@ -167,15 +167,15 @@ namespace :geo do
state = State.find_by(code: data["state_code"])
city = City.find_or_create_by!(name: data["name"]) do |c|
- c.country_id = country.id
+ c.country_id = country.id,
+ c.latitude = data["latitude"],
+ c.longitude = data["longitude"]
end
puts "Syncing City[#{count}/#{sum}] [#{data["name"]}] Country:[#{country&.name}] "
count += 1
city.update!(
- latitude: data["latitude"],
- longitude: data["longitude"],
flag: data["flag"] || true,
wiki_data_id: data["wikiDataId"]
)
diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml
new file mode 100644
index 0000000..e11ea93
--- /dev/null
+++ b/test/fixtures/users.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+# This model initially had no columns defined. If you add columns to the
+# model remove the "{}" from the fixture names and add the columns immediately
+# below each fixture, per the syntax in the comments below
+#
+#one: {}
+# column: value
+#
+#two: {}
+# column: value
diff --git a/test/models/user_test.rb b/test/models/user_test.rb
new file mode 100644
index 0000000..5c07f49
--- /dev/null
+++ b/test/models/user_test.rb
@@ -0,0 +1,7 @@
+require "test_helper"
+
+class UserTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end