Skip to content

Commit

Permalink
Merge pull request #142 from loftwah/dl/offline-geocoder
Browse files Browse the repository at this point in the history
Dl/offline geocoder
  • Loading branch information
loftwah authored Sep 14, 2024
2 parents c1b7a8e + 8ac3659 commit 21dc783
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 89 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ gem 'redis', '>= 4.0.1'
gem 'chartkick'
gem 'devise'
gem 'font-awesome-sass', '~> 6.5.2'
gem 'geocoder'
gem 'offline_geocoder'
gem 'groupdate'
gem 'mini_magick'
gem 'sidekiq'
Expand Down
15 changes: 10 additions & 5 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ GEM
concurrent-ruby (1.3.3)
connection_pool (2.4.1)
crass (1.0.6)
csv (3.3.0)
date (3.3.4)
debug (1.9.2)
irb (~> 1.10)
Expand Down Expand Up @@ -141,14 +140,18 @@ GEM
ffi (1.17.0-x86-linux-gnu)
ffi (1.17.0-x86_64-darwin)
ffi (1.17.0-x86_64-linux-gnu)
ffi-compiler (1.3.2)
ffi (>= 1.15.5)
rake
font-awesome-sass (6.5.2)
sassc (~> 2.0)
fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4)
geocoder (1.8.3)
base64 (>= 0.1.0)
csv (>= 3.0.0)
geokdtree (0.2.0)
ffi
ffi-compiler
rake
globalid (1.2.1)
activesupport (>= 6.1)
groupdate (6.4.0)
Expand Down Expand Up @@ -205,6 +208,8 @@ GEM
racc (~> 1.4)
nokogiri (1.16.6-x86_64-linux)
racc (~> 1.4)
offline_geocoder (0.2.0)
geokdtree (~> 0.2)
orm_adapter (0.5.0)
psych (5.1.2)
stringio
Expand Down Expand Up @@ -385,11 +390,11 @@ DEPENDENCIES
devise
factory_bot_rails
font-awesome-sass (~> 6.5.2)
geocoder
groupdate
importmap-rails
jbuilder
mini_magick
offline_geocoder
puma (>= 5.0)
rails (~> 7.1.3, >= 7.1.3.4)
redis (>= 4.0.1)
Expand Down
11 changes: 0 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@ Create a `.env` file in the root directory with the following variables:
```
SECRET_KEY_BASE=your_secret_key_base
AXIOM_API_KEY=your_axiom_api_key
GEOCODER_API_KEY=your_geocoder_api_key
DO_TOKEN=your_digitalocean_token
SPACES_ACCESS_KEY_ID=your_spaces_access_key_id
SPACES_SECRET_ACCESS_KEY=your_spaces_secret_access_key
Expand Down Expand Up @@ -287,16 +286,6 @@ The restore process:
2. Loads the specified backup file.
3. Applies any pending migrations.

## Geolocation

Geolocation is currently a mandatory feature in Linkarooie. It uses the `geocoder` gem to provide location-based insights for link clicks and page views.

To enable geolocation:
1. Obtain a free API key from [ipapi](https://ipapi.com).
2. Set the `GEOCODER_API_KEY` environment variable with your API key.

Future plans include making geolocation optional to cater to different privacy preferences.

## Customization

Linkarooie is designed to be highly customizable:
Expand Down
11 changes: 11 additions & 0 deletions app/controllers/analytics_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def index
@daily_views = fetch_cached_data("daily_views") { fetch_daily_views }
@daily_unique_visitors = fetch_cached_data("daily_unique_visitors") { fetch_daily_unique_visitors }
@browser_data = fetch_cached_data("browser_data") { fetch_browser_data }
@location_data = fetch_cached_data("location_data") { fetch_location_data }
end

private
Expand All @@ -35,6 +36,16 @@ def fetch_cached_data(key, &block)
Rails.cache.fetch("#{cache_key_with_version}/#{key}", expires_in: CACHE_EXPIRATION, &block)
end

def fetch_location_data
@user.page_views.group(:country, :city).count.map do |location, count|
{
country: location[0],
city: location[1],
count: count
}
end.sort_by { |location| -location[:count] }.take(10)
end

# Update this method to exclude hidden links
def fetch_link_analytics
@user.links.where(hidden: false).includes(:link_clicks).map do |link|
Expand Down
13 changes: 12 additions & 1 deletion app/middleware/page_view_tracker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,28 @@ def html_response?(headers)
def track_page_view(request)
user = User.find_by(username: request.path.split('/').last)
if user
location = OFFLINE_GEOCODER.search(request.ip)

PageView.create(
user: user,
path: request.path,
referrer: request.referrer,
browser: request.user_agent,
visited_at: Time.current,
ip_address: request.ip,
session_id: request.session[:session_id]
session_id: request.session[:session_id],
country: location[:country],
city: location[:name],
state: location[:admin1],
county: location[:admin2],
latitude: location[:lat],
longitude: location[:lon],
country_code: location[:cc]
)
end
rescue ActiveRecord::RecordNotUnique
Rails.logger.info "Duplicate page view detected and ignored"
rescue => e
Rails.logger.error "Error tracking page view: #{e.message}"
end
end
120 changes: 72 additions & 48 deletions app/views/analytics/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
</span>
<% end %>
</h1>
</div>

<!-- Overall Metrics -->
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4 mb-6">
Expand Down Expand Up @@ -54,6 +53,78 @@
</div>
<% end %>

<!-- Charts Section -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<!-- Time-based Analytics -->
<div class="bg-gray-800 rounded-lg shadow p-4">
<h2 class="text-xl font-semibold mb-4">Daily Views (Last 30 Days)</h2>
<%= line_chart @daily_views,
colors: ["#84CC16"],
library: {
backgroundColor: 'transparent',
legend: { display: false },
scales: {
x: { ticks: { color: 'white', fontSize: 12 } },
y: { ticks: { color: 'white', fontSize: 12 } },
},
elements: {
line: { tension: 0.4 },
point: { radius: 3, backgroundColor: 'white' }
},
title: { display: true, text: 'Daily Views (Last 30 Days)', color: 'white', fontSize: 16 },
responsive: true
},
height: "300px" %>
</div>

<!-- Unique Visitors Chart -->
<div class="bg-gray-800 rounded-lg shadow p-4">
<h2 class="text-xl font-semibold mb-4">Unique Visitors (Last 30 Days)</h2>
<%= line_chart @daily_unique_visitors,
colors: ["#E879F9"],
library: {
backgroundColor: 'transparent',
legend: { display: false },
scales: {
x: { ticks: { color: 'white', fontSize: 12 } },
y: { ticks: { color: 'white', fontSize: 12 } },
},
elements: {
line: { tension: 0.4 },
point: { radius: 3, backgroundColor: 'white' }
},
title: { display: true, text: 'Unique Visitors (Last 30 Days)', color: 'white', fontSize: 16 },
responsive: true
},
height: "300px" %>
</div>
</div>

<!-- Top Visitor Locations (New Section) -->
<div class="bg-gray-800 rounded-lg shadow p-4 mb-6">
<h2 class="text-xl font-semibold mb-4">Top Visitor Locations</h2>
<div class="overflow-x-auto">
<table class="w-full text-sm text-center table-auto">
<thead class="text-xs uppercase bg-gray-700">
<tr>
<th scope="col" class="px-4 py-3 rounded-tl-lg">City</th>
<th scope="col" class="px-4 py-3">Country</th>
<th scope="col" class="px-4 py-3 rounded-tr-lg">Views</th>
</tr>
</thead>
<tbody>
<% @location_data.each_with_index do |location, index| %>
<tr class="<%= index.even? ? 'bg-gray-800' : 'bg-gray-900' %> border-b border-gray-700">
<td class="px-4 py-3"><%= location[:city] || 'Unknown' %></td>
<td class="px-4 py-3"><%= location[:country] || 'Unknown' %></td>
<td class="px-4 py-3"><%= number_with_delimiter(location[:count]) %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>

<!-- Link Analytics Table -->
<div class="bg-gray-800 rounded-lg shadow p-4 mb-6">
<h2 class="text-xl font-semibold mb-4">Link Analytics</h2>
Expand Down Expand Up @@ -110,53 +181,6 @@
</div>
</div>

<!-- Charts Section -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<!-- Time-based Analytics -->
<div class="bg-gray-800 rounded-lg shadow p-4">
<h2 class="text-xl font-semibold mb-4">Daily Views (Last 30 Days)</h2>
<%= line_chart @daily_views,
colors: ["#84CC16"],
library: {
backgroundColor: 'transparent',
legend: { display: false },
scales: {
x: { ticks: { color: 'white', fontSize: 12 } },
y: { ticks: { color: 'white', fontSize: 12 } },
},
elements: {
line: { tension: 0.4 },
point: { radius: 3, backgroundColor: 'white' }
},
title: { display: true, text: 'Daily Views (Last 30 Days)', color: 'white', fontSize: 16 },
responsive: true
},
height: "300px" %>
</div>

<!-- Unique Visitors Chart -->
<div class="bg-gray-800 rounded-lg shadow p-4">
<h2 class="text-xl font-semibold mb-4">Unique Visitors (Last 30 Days)</h2>
<%= line_chart @daily_unique_visitors,
colors: ["#E879F9"],
library: {
backgroundColor: 'transparent',
legend: { display: false },
scales: {
x: { ticks: { color: 'white', fontSize: 12 } },
y: { ticks: { color: 'white', fontSize: 12 } },
},
elements: {
line: { tension: 0.4 },
point: { radius: 3, backgroundColor: 'white' }
},
title: { display: true, text: 'Unique Visitors (Last 30 Days)', color: 'white', fontSize: 16 },
responsive: true
},
height: "300px" %>
</div>
</div>

<!-- Browser Usage -->
<div class="grid grid-cols-1 gap-6">
<div class="bg-gray-800 rounded-lg shadow p-4">
Expand Down
22 changes: 0 additions & 22 deletions config/initializers/geocoder.rb

This file was deleted.

2 changes: 2 additions & 0 deletions config/initializers/offline_geocoder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require 'offline_geocoder'
OFFLINE_GEOCODER = OfflineGeocoder.new
11 changes: 11 additions & 0 deletions db/migrate/20240914101600_add_location_details_to_page_views.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class AddLocationDetailsToPageViews < ActiveRecord::Migration[7.1]
def change
add_column :page_views, :country, :string
add_column :page_views, :city, :string
add_column :page_views, :state, :string
add_column :page_views, :county, :string
add_column :page_views, :latitude, :float
add_column :page_views, :longitude, :float
add_column :page_views, :country_code, :string
end
end
9 changes: 8 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 21dc783

Please sign in to comment.