mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 01:01:39 -05:00
Add places page
This commit is contained in:
parent
0e90bdf15d
commit
7d7005063b
20 changed files with 202 additions and 52 deletions
|
|
@ -22,7 +22,7 @@ The visit suggestion release.
|
|||
- [x] Make it look acceptable
|
||||
- [ ] Make visits suggestion an idempotent process
|
||||
- [ ] Places management: if visit is detected at a place with a name, suggest this name as a visit name
|
||||
- [ ] Separate page for places management
|
||||
- [x] Separate page for places management
|
||||
|
||||
|
||||
### Added
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,8 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::AreasController < ApplicationController
|
||||
skip_forgery_protection
|
||||
before_action :authenticate_api_key
|
||||
class Api::V1::AreasController < ApiController
|
||||
before_action :set_area, only: %i[update destroy]
|
||||
|
||||
def index
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Overland::BatchesController < ApplicationController
|
||||
skip_forgery_protection
|
||||
before_action :authenticate_api_key
|
||||
|
||||
class Api::V1::Overland::BatchesController < ApiController
|
||||
def create
|
||||
Overland::BatchCreatingJob.perform_later(batch_params, current_api_user.id)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Owntracks::PointsController < ApplicationController
|
||||
skip_forgery_protection
|
||||
before_action :authenticate_api_key
|
||||
|
||||
class Api::V1::Owntracks::PointsController < ApiController
|
||||
def create
|
||||
Owntracks::PointCreatingJob.perform_later(point_params, current_api_user.id)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::PointsController < ApplicationController
|
||||
skip_forgery_protection
|
||||
before_action :authenticate_api_key
|
||||
|
||||
class Api::V1::PointsController < ApiController
|
||||
def index
|
||||
start_at = params[:start_at]&.to_datetime&.to_i
|
||||
end_at = params[:end_at]&.to_datetime&.to_i || Time.zone.now.to_i
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::StatsController < ApplicationController
|
||||
skip_forgery_protection
|
||||
before_action :authenticate_api_key
|
||||
|
||||
class Api::V1::StatsController < ApiController
|
||||
def index
|
||||
render json: StatsSerializer.new(current_api_user).call
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::VisitsController < ApplicationController
|
||||
skip_forgery_protection
|
||||
before_action :authenticate_api_key
|
||||
|
||||
class Api::V1::VisitsController < ApiController
|
||||
def update
|
||||
visit = current_api_user.visits.find(params[:id])
|
||||
visit = update_visit(visit)
|
||||
|
|
@ -23,7 +20,7 @@ class Api::V1::VisitsController < ApplicationController
|
|||
visit.name = visit.place.name if visit_params[:place_id].present?
|
||||
end
|
||||
|
||||
visit.save
|
||||
visit.save!
|
||||
|
||||
visit
|
||||
end
|
||||
|
|
|
|||
18
app/controllers/api_controller.rb
Normal file
18
app/controllers/api_controller.rb
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ApiController < ApplicationController
|
||||
skip_before_action :verify_authenticity_token
|
||||
before_action :authenticate_api_key
|
||||
|
||||
private
|
||||
|
||||
def authenticate_api_key
|
||||
return head :unauthorized unless current_api_user
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def current_api_user
|
||||
@current_api_user ||= User.find_by(api_key: params[:api_key])
|
||||
end
|
||||
end
|
||||
|
|
@ -18,14 +18,4 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
redirect_to root_path, notice: 'You are not authorized to perform this action.', status: :see_other
|
||||
end
|
||||
|
||||
def authenticate_api_key
|
||||
return head :unauthorized unless current_api_user
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def current_api_user
|
||||
@current_api_user ||= User.find_by(api_key: params[:api_key])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
22
app/controllers/places_controller.rb
Normal file
22
app/controllers/places_controller.rb
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PlacesController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_place, only: :destroy
|
||||
|
||||
def index
|
||||
@places = current_user.places.page(params[:page]).per(20)
|
||||
end
|
||||
|
||||
def destroy
|
||||
@place.destroy!
|
||||
|
||||
redirect_to places_url, notice: 'Place was successfully destroyed.', status: :see_other
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_place
|
||||
@place = current_user.places.find(params[:id])
|
||||
end
|
||||
end
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
module ExportsHelper
|
||||
end
|
||||
|
|
@ -6,14 +6,15 @@ class User < ApplicationRecord
|
|||
devise :database_authenticatable, :registerable,
|
||||
:recoverable, :rememberable, :validatable
|
||||
|
||||
has_many :imports, dependent: :destroy
|
||||
has_many :points, through: :imports
|
||||
has_many :stats, dependent: :destroy
|
||||
has_many :tracked_points, class_name: 'Point', dependent: :destroy
|
||||
has_many :exports, dependent: :destroy
|
||||
has_many :notifications, dependent: :destroy
|
||||
has_many :areas, dependent: :destroy
|
||||
has_many :visits, dependent: :destroy
|
||||
has_many :imports, dependent: :destroy
|
||||
has_many :stats, dependent: :destroy
|
||||
has_many :exports, dependent: :destroy
|
||||
has_many :notifications, dependent: :destroy
|
||||
has_many :areas, dependent: :destroy
|
||||
has_many :visits, dependent: :destroy
|
||||
has_many :points, through: :imports
|
||||
has_many :places, through: :visits
|
||||
|
||||
after_create :create_api_key
|
||||
|
||||
|
|
|
|||
52
app/views/places/index.html.erb
Normal file
52
app/views/places/index.html.erb
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<% content_for :title, "Places" %>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="flex justify-center my-5">
|
||||
<h1 class="font-bold text-4xl">Places</h1>
|
||||
</div>
|
||||
|
||||
<div id="places" class="min-w-full">
|
||||
<% if @places.empty? %>
|
||||
<div class="hero min-h-80 bg-base-200">
|
||||
<div class="hero-content text-center">
|
||||
<div class="max-w-md">
|
||||
<h1 class="text-5xl font-bold">Hello there!</h1>
|
||||
<p class="py-6">
|
||||
Here you'll find your places, created by Visits suggestion process. But now there are none.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="flex justify-center my-5">
|
||||
<div class='flex'>
|
||||
<%= paginate @places %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Created at</th>
|
||||
<th>Coordinates</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @places.each do |place| %>
|
||||
<tr>
|
||||
<td><%= place.name %></td>
|
||||
<td><%= place.created_at.strftime('%Y-%m-%d %H:%M:%S') %></td>
|
||||
<td><%= place.to_coordinates.map(&:to_f) %></td>
|
||||
<td>
|
||||
<%= link_to 'Delete', place, data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?", turbo_method: :delete }, method: :delete, class: "px-4 py-2 bg-red-500 text-white rounded-md" %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -8,7 +8,8 @@
|
|||
<li><%= link_to 'Map', map_url, class: "#{active_class?(map_url)}" %></li>
|
||||
<li><%= link_to 'Points', points_url, class: "#{active_class?(points_url)}" %></li>
|
||||
<li><%= link_to 'Stats', stats_url, class: "#{active_class?(stats_url)}" %></li>
|
||||
<li><%= link_to 'Visits<sup>β</sup>'.html_safe, visits_url, class: "#{active_class?(visits_url)}" %></li>
|
||||
<li><%= link_to 'Visits<sup>β</sup>'.html_safe, visits_url(status: :confirmed), class: "#{active_class?(visits_url)}" %></li>
|
||||
<li><%= link_to 'Places', places_url, class: "#{active_class?(places_url)}" %></li>
|
||||
<li><%= link_to 'Imports', imports_url, class: "#{active_class?(imports_url)}" %></li>
|
||||
<li><%= link_to 'Exports', exports_url, class: "#{active_class?(exports_url)}" %></li>
|
||||
</ul>
|
||||
|
|
@ -42,7 +43,8 @@
|
|||
<li><%= link_to 'Map', map_url, class: "#{active_class?(map_url)}" %></li>
|
||||
<li><%= link_to 'Points', points_url, class: "#{active_class?(points_url)}" %></li>
|
||||
<li><%= link_to 'Stats', stats_url, class: "#{active_class?(stats_url)}" %></li>
|
||||
<li><%= link_to 'Visits<sup>β</sup>'.html_safe, visits_url, class: "#{active_class?(visits_url)}" %></li>
|
||||
<li><%= link_to 'Visits<sup>β</sup>'.html_safe, visits_url(status: :confirmed), class: "#{active_class?(visits_url)}" %></li>
|
||||
<li><%= link_to 'Places', places_url, class: "#{active_class?(places_url)}" %></li>
|
||||
<li><%= link_to 'Imports', imports_url, class: "#{active_class?(imports_url)}" %></li>
|
||||
<li><%= link_to 'Exports', exports_url, class: "#{active_class?(exports_url)}" %></li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ Rails.application.routes.draw do
|
|||
|
||||
resources :imports
|
||||
resources :visits, only: %i[index update]
|
||||
resources :places, only: %i[index destroy]
|
||||
resources :exports, only: %i[index create destroy]
|
||||
resources :points, only: %i[index] do
|
||||
collection do
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ RSpec.describe User, type: :model do
|
|||
it { is_expected.to have_many(:notifications).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:areas).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:visits).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:places).through(:visits) }
|
||||
end
|
||||
|
||||
describe 'callbacks' do
|
||||
|
|
|
|||
|
|
@ -1,7 +1,50 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe "Api::V1::Visits", type: :request do
|
||||
describe "GET /index" do
|
||||
pending "add some examples (or delete) #{__FILE__}"
|
||||
RSpec.describe 'Api::V1::Visits', type: :request do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
let(:api_key) { user.api_key }
|
||||
describe 'PUT /api/v1/visits/:id' do
|
||||
let(:visit) { create(:visit, user:) }
|
||||
|
||||
let(:valid_attributes) do
|
||||
{
|
||||
visit: {
|
||||
name: 'New name'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
let(:invalid_attributes) do
|
||||
{
|
||||
visit: {
|
||||
name: nil
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'with valid parameters' do
|
||||
it 'updates the requested visit' do
|
||||
put api_v1_visit_url(visit, api_key:), params: valid_attributes
|
||||
|
||||
expect(visit.reload.name).to eq('New name')
|
||||
end
|
||||
|
||||
it 'renders a JSON response with the visit' do
|
||||
put api_v1_visit_url(visit, api_key:), params: valid_attributes
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid parameters' do
|
||||
it 'renders a JSON response with errors for the visit' do
|
||||
put api_v1_visit_url(visit, api_key:), params: invalid_attributes
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
39
spec/requests/places_spec.rb
Normal file
39
spec/requests/places_spec.rb
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe '/places', type: :request do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_request(:any, 'https://api.github.com/repos/Freika/dawarich/tags')
|
||||
.to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {})
|
||||
|
||||
sign_in user
|
||||
end
|
||||
|
||||
describe 'GET /index' do
|
||||
it 'renders a successful response' do
|
||||
get places_url
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /destroy' do
|
||||
let!(:place) { create(:place) }
|
||||
let!(:visit) { create(:visit, place:, user:) }
|
||||
|
||||
it 'destroys the requested place' do
|
||||
expect do
|
||||
delete place_url(place)
|
||||
end.to change(Place, :count).by(-1)
|
||||
end
|
||||
|
||||
it 'redirects to the places list' do
|
||||
delete place_url(place)
|
||||
|
||||
expect(response).to redirect_to(places_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -42,8 +42,8 @@ RSpec.describe Visits::Suggest do
|
|||
expect { subject }.to change(Notification, :count).by(1)
|
||||
end
|
||||
|
||||
it 'does not reverse geocodes visits' do
|
||||
expect_any_instance_of(Visit).to_not receive(:async_reverse_geocode).and_call_original
|
||||
it 'reverse geocodes visits' do
|
||||
expect_any_instance_of(Visit).to receive(:async_reverse_geocode).and_call_original
|
||||
|
||||
subject
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue