diff --git a/.env.example b/.env.example
index f17d64a5a..8279150e5 100644
--- a/.env.example
+++ b/.env.example
@@ -35,5 +35,8 @@ TWITTER_ACCESS_SECRET=
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHANNEL=
+FATHOM_SITE_ID=
+FATHOM_TOKEN=
+
FLARE_KEY=
VITE_FLARE_KEY="${FLARE_KEY}"
diff --git a/README.md b/README.md
index 82c39de39..07d598285 100644
--- a/README.md
+++ b/README.md
@@ -113,6 +113,15 @@ TELEGRAM_BOT_TOKEN=
TELEGRAM_CHANNEL=
```
+### Fathom Analytics (optional)
+
+To enable view counts on articles, you'll need to register a [Fathom Analytics](https://app.usefathom.com/register) account and [install](https://usefathom.com/docs/start/install) it on the site. You will then need to create an API token and find your site ID before updating the below environment variables in your `.env` file.
+
+```
+FATHOM_SITE_ID=
+FATHOM_TOKEN=
+```
+
## Commands
Command | Description
diff --git a/app/Console/Commands/GenerateSitemap.php b/app/Console/Commands/GenerateSitemap.php
index 9186f274b..4f5c08fb8 100644
--- a/app/Console/Commands/GenerateSitemap.php
+++ b/app/Console/Commands/GenerateSitemap.php
@@ -10,7 +10,7 @@
class GenerateSitemap extends Command
{
- protected $signature = 'sitemap:generate';
+ protected $signature = 'lio:generate-sitemap';
protected $description = 'Crawl the site to generate a sitemap.xml file';
diff --git a/app/Console/Commands/PostArticleToTwitter.php b/app/Console/Commands/PostArticleToTwitter.php
index eca5b4971..a5a94750a 100644
--- a/app/Console/Commands/PostArticleToTwitter.php
+++ b/app/Console/Commands/PostArticleToTwitter.php
@@ -9,7 +9,7 @@
final class PostArticleToTwitter extends Command
{
- protected $signature = 'post-article-to-twitter';
+ protected $signature = 'lio:post-article-to-twitter';
protected $description = 'Posts the latest unshared article to Twitter';
diff --git a/app/Console/Commands/UpdateArticleViewCounts.php b/app/Console/Commands/UpdateArticleViewCounts.php
new file mode 100644
index 000000000..c75aa59a2
--- /dev/null
+++ b/app/Console/Commands/UpdateArticleViewCounts.php
@@ -0,0 +1,68 @@
+siteId = config('services.fathom.site_id');
+ $this->token = config('services.fathom.token');
+ }
+
+ public function handle()
+ {
+ if (! $this->siteId || ! $this->token) {
+ $this->error('Fathom site ID and token must be configured');
+
+ return;
+ }
+
+ Article::published()->chunk(100, function ($articles) {
+ $articles->each(function ($article) {
+ $article->update([
+ 'view_count' => $this->getViewCountFor($article),
+ ]);
+ });
+ });
+ }
+
+ protected function getViewCountFor(Article $article): ?int
+ {
+ $response = Http::withToken($this->token)
+ ->get('https://api.usefathom.com/v1/aggregations', [
+ 'date_from' => '2021-03-01 00:00:00', // Fathom data aggregations not accurate prior to this date.
+ 'field_grouping' => 'pathname',
+ 'entity' => 'pageview',
+ 'aggregates' => 'pageviews,visits,uniques',
+ 'entity_id' => $this->siteId,
+ 'filters' => json_encode([
+ [
+ 'property' => 'pathname',
+ 'operator' => 'is',
+ 'value' => "/articles/{$article->slug()}",
+ ],
+ ]),
+ ]);
+
+ if ($response->failed()) {
+ return null;
+ }
+
+ return $response->json('0.pageviews');
+ }
+}
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index f2474ae03..ffa6dabd9 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -16,8 +16,9 @@ protected function schedule(Schedule $schedule)
$schedule->command('schedule-monitor:sync')->dailyAt('04:56');
$schedule->command('model:prune', ['--model' => MonitoredScheduledTaskLogItem::class])->daily();
$schedule->command('horizon:snapshot')->everyFiveMinutes();
- $schedule->command('post-article-to-twitter')->twiceDaily(14, 18);
- $schedule->command('sitemap:generate')->daily()->graceTimeInMinutes(25);
+ $schedule->command('lio:post-article-to-twitter')->twiceDaily(14, 18);
+ $schedule->command('lio:generate-sitemap')->daily()->graceTimeInMinutes(25);
+ $schedule->command('lio:update-article-view-counts')->twiceDaily();
}
/**
diff --git a/app/Models/Article.php b/app/Models/Article.php
index b04e0e215..857cdace3 100644
--- a/app/Models/Article.php
+++ b/app/Models/Article.php
@@ -46,6 +46,7 @@ final class Article extends Model implements Feedable
'slug',
'hero_image',
'is_pinned',
+ 'view_count',
'tweet_id',
'submitted_at',
'approved_at',
@@ -192,6 +193,11 @@ public function readTime()
return $minutes == 0 ? 1 : $minutes;
}
+ public function viewCount()
+ {
+ return number_format($this->view_count);
+ }
+
public function isUpdated(): bool
{
return $this->updated_at->gt($this->created_at);
diff --git a/config/services.php b/config/services.php
index ef09a4133..b343f6757 100644
--- a/config/services.php
+++ b/config/services.php
@@ -57,4 +57,9 @@
'channel' => env('TELEGRAM_CHANNEL'),
],
+ 'fathom' => [
+ 'site_id' => env('FATHOM_SITE_ID'),
+ 'token' => env('FATHOM_TOKEN'),
+ ],
+
];
diff --git a/database/migrations/2022_07_09_191433_update_articles_table_add_view_count.php b/database/migrations/2022_07_09_191433_update_articles_table_add_view_count.php
new file mode 100644
index 000000000..bf563776b
--- /dev/null
+++ b/database/migrations/2022_07_09_191433_update_articles_table_add_view_count.php
@@ -0,0 +1,15 @@
+bigInteger('view_count')->nullable()->after('is_pinned');
+ });
+ }
+};
diff --git a/phpunit.xml b/phpunit.xml
index 185c04c2a..66d1728d8 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -28,5 +28,7 @@