From 1574915d68c9b9f37edf62a93d2bf197d0c6851f Mon Sep 17 00:00:00 2001 From: Felipe Barso <77860630+aprendendofelipe@users.noreply.github.com> Date: Wed, 18 Dec 2024 08:23:22 -0300 Subject: [PATCH 1/2] feat(umami): add Umami Analytics Server via Docker for Dev and Tests --- .env | 6 ++ infra/docker-compose.development.yml | 17 ++++ infra/scripts/01-create-umami-database.sql | 1 + infra/scripts/config-umami.js | 93 ++++++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 infra/scripts/01-create-umami-database.sql create mode 100644 infra/scripts/config-umami.js diff --git a/.env b/.env index e233dd888..08bb6336e 100644 --- a/.env +++ b/.env @@ -16,3 +16,9 @@ EMAIL_HTTP_PORT=1080 EMAIL_USER= EMAIL_PASSWORD= UNDER_MAINTENANCE={"methodsAndPaths":["POST /api/v1/under-maintenance-test$"]} + +# Umami Analytics +UMAMI_DB=umami +UMAMI_PORT=3001 +UMAMI_ENDPOINT=http://localhost:$UMAMI_PORT +NEXT_PUBLIC_UMAMI_WEBSITE_ID=0d54205e-06f1-49b0-89e2-1fc412e52a80 diff --git a/infra/docker-compose.development.yml b/infra/docker-compose.development.yml index ee26d2b38..f7931d3e6 100644 --- a/infra/docker-compose.development.yml +++ b/infra/docker-compose.development.yml @@ -9,7 +9,13 @@ services: - '${POSTGRES_PORT}:5432' volumes: - postgres_data:/data/postgres + - ./scripts:/docker-entrypoint-initdb.d restart: unless-stopped + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'] + interval: 5s + timeout: 5s + retries: 5 mailcatcher: container_name: mailcatcher image: sj26/mailcatcher @@ -19,5 +25,16 @@ services: ports: - '${EMAIL_SMTP_PORT}:1025' - '${EMAIL_HTTP_PORT}:1080' + umami: + image: ghcr.io/umami-software/umami:postgresql-latest + ports: + - '${UMAMI_PORT}:3000' + environment: + DATABASE_URL: postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres_dev:5432/$UMAMI_DB + depends_on: + postgres_dev: + condition: service_healthy + init: true + restart: unless-stopped volumes: postgres_data: diff --git a/infra/scripts/01-create-umami-database.sql b/infra/scripts/01-create-umami-database.sql new file mode 100644 index 000000000..6c4990a2e --- /dev/null +++ b/infra/scripts/01-create-umami-database.sql @@ -0,0 +1 @@ +CREATE DATABASE umami; diff --git a/infra/scripts/config-umami.js b/infra/scripts/config-umami.js new file mode 100644 index 000000000..15d361c8c --- /dev/null +++ b/infra/scripts/config-umami.js @@ -0,0 +1,93 @@ +/* eslint-disable no-console */ +const { Client } = require('pg'); + +const endpoint = process.env.UMAMI_ENDPOINT; +const websiteDomain = `${process.env.NEXT_PUBLIC_WEBSERVER_HOST}:${process.env.NEXT_PUBLIC_WEBSERVER_PORT}`; +const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID; +const connectionString = `postgres://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@${process.env.POSTGRES_HOST}:${process.env.POSTGRES_PORT}/${process.env.UMAMI_DB}`; +const username = process.env.UMAMI_API_CLIENT_USERNAME || 'admin'; +const password = process.env.UMAMI_API_CLIENT_PASSWORD || 'umami'; + +const client = new Client({ + connectionString, + connectionTimeoutMillis: 5000, + idleTimeoutMillis: 30000, + allowExitOnIdle: false, +}); + +configUmami(); + +async function configUmami() { + console.log('\n> Waiting for Umami Server to start...'); + console.log('> Endpoint:', endpoint); + + await waitForServer(); + + console.log('> Creating Umami configuration...'); + + const token = await fetch(`${endpoint}/api/auth/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username, + password, + }), + }) + .then((res) => res.json()) + .then((data) => data.token); + + console.log('> Token:', token); + + const websites = await fetch(`${endpoint}/api/websites`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((res) => res.json()) + .then((data) => data.data); + + let existDevWebisite; + + if (websites.length) { + existDevWebisite = websites.some((site) => site.id === websiteId); + } + + await client.connect(); + + if (!existDevWebisite) { + const website = await fetch(`${endpoint}/api/websites`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + name: 'TabNews Dev', + domain: websiteDomain, + }), + }).then((res) => res.json()); + + await client.query('UPDATE website SET website_id = $1 WHERE website_id = $2;', [websiteId, website.id]); + } + + await client.end(); + + console.log('> Umami configuration created!'); +} + +async function waitForServer(attempts = 5) { + try { + return await fetch(`${endpoint}/api/heartbeat`); + } catch (error) { + if (attempts > 1) { + console.log('> Umami is not ready, waiting...'); + await new Promise((resolve) => setTimeout(resolve, 1000)); + return waitForServer(attempts - 1); + } + + console.error('🔴 Umami is not ready, exiting...'); + process.exit(1); + } +} From 8235c6e0e216b0ce42f993acdb1bf0fb465b7344 Mon Sep 17 00:00:00 2001 From: Felipe Barso <77860630+aprendendofelipe@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:49:41 -0300 Subject: [PATCH 2/2] feat(analytics): add Umami tracker to `/login`, `/cadastro` and `/publicar` pages --- next.config.js | 4 + pages/_app.public.js | 13 +- pages/interface/components/Analytics/index.js | 26 ++ pages/interface/index.js | 1 + public/analytics.js | 277 ++++++++++++++++++ 5 files changed, 310 insertions(+), 11 deletions(-) create mode 100644 pages/interface/components/Analytics/index.js create mode 100644 public/analytics.js diff --git a/next.config.js b/next.config.js index 2fe5e5325..fb78be929 100644 --- a/next.config.js +++ b/next.config.js @@ -59,6 +59,10 @@ module.exports = { source: '/recentes/rss', destination: '/api/v1/contents/rss', }, + { + source: '/api/v1/analytics', + destination: `${process.env.UMAMI_ENDPOINT}/api/send`, + }, ]; }, headers() { diff --git a/pages/_app.public.js b/pages/_app.public.js index 0e825634a..254ddf2c2 100644 --- a/pages/_app.public.js +++ b/pages/_app.public.js @@ -1,9 +1,8 @@ -import { Analytics } from '@vercel/analytics/react'; import { RevalidateProvider } from 'next-swr'; import { SWRConfig } from 'swr'; import { ThemeProvider, Turnstile } from '@/TabNewsUI'; -import { DefaultHead, UserProvider } from 'pages/interface'; +import { Analytics, DefaultHead, UserProvider } from 'pages/interface'; async function SWRFetcher(resource, init) { const response = await fetch(resource, init); @@ -30,15 +29,7 @@ function MyApp({ Component, pageProps }) { - { - const { pathname } = new URL(event.url); - if (['/', '/publicar'].includes(pathname)) { - return null; - } - return event; - }} - /> + ); diff --git a/pages/interface/components/Analytics/index.js b/pages/interface/components/Analytics/index.js new file mode 100644 index 000000000..302fdb3c6 --- /dev/null +++ b/pages/interface/components/Analytics/index.js @@ -0,0 +1,26 @@ +import { Analytics as VercelAnalytics } from '@vercel/analytics/react'; +import Script from 'next/script'; + +export default function Analytics() { + return ( + <> +