From 9d10fda3f39c2ac0de30aa2ff05f0e6dcd200b24 Mon Sep 17 00:00:00 2001 From: Web-serfer Date: Sun, 3 May 2026 17:16:37 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=BE=D0=B2=D1=8B=D0=B9=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BF=D0=BE=D0=B5=D0=BD=D1=82=20=D1=81=D1=87=D0=B5=D1=82?= =?UTF-8?q?=D1=87=D0=B8=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../1777809787_created_site_visitors.js | 103 +++++++++++++++ .../1777809835_updated_site_visitors.js | 28 +++++ bun.lockb | Bin 0 -> 91616 bytes .../src/components/base/VisitorCounter.astro | 77 ++++++++++++ .../components/layouts/footer/Footer.astro | 12 +- frontend/src/icons/users-group.svg | 6 + frontend/src/icons/users-total.svg | 6 + frontend/src/pages/api/visitors.ts | 117 ++++++++++++++++++ package.json | 5 +- 9 files changed, 349 insertions(+), 5 deletions(-) create mode 100644 backend/pb_migrations/1777809787_created_site_visitors.js create mode 100644 backend/pb_migrations/1777809835_updated_site_visitors.js create mode 100644 bun.lockb create mode 100644 frontend/src/components/base/VisitorCounter.astro create mode 100644 frontend/src/icons/users-group.svg create mode 100644 frontend/src/icons/users-total.svg create mode 100644 frontend/src/pages/api/visitors.ts diff --git a/backend/pb_migrations/1777809787_created_site_visitors.js b/backend/pb_migrations/1777809787_created_site_visitors.js new file mode 100644 index 0000000..3e5cd05 --- /dev/null +++ b/backend/pb_migrations/1777809787_created_site_visitors.js @@ -0,0 +1,103 @@ +/// +migrate((app) => { + const collection = new Collection({ + "createRule": null, + "deleteRule": null, + "fields": [ + { + "autogeneratePattern": "[a-z0-9]{15}", + "help": "", + "hidden": false, + "id": "text3208210256", + "max": 15, + "min": 15, + "name": "id", + "pattern": "^[a-z0-9]+$", + "presentable": false, + "primaryKey": true, + "required": true, + "system": true, + "type": "text" + }, + { + "autogeneratePattern": "", + "help": "", + "hidden": false, + "id": "text791980464", + "max": 0, + "min": 0, + "name": "visitor_hash", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "help": "", + "hidden": false, + "id": "text2783163181", + "max": 0, + "min": 0, + "name": "ip", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "autogeneratePattern": "", + "help": "", + "hidden": false, + "id": "text3293145029", + "max": 0, + "min": 0, + "name": "user_agent", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + }, + { + "hidden": false, + "id": "autodate2990389176", + "name": "created", + "onCreate": true, + "onUpdate": false, + "presentable": false, + "system": false, + "type": "autodate" + }, + { + "hidden": false, + "id": "autodate3332085495", + "name": "updated", + "onCreate": true, + "onUpdate": true, + "presentable": false, + "system": false, + "type": "autodate" + } + ], + "id": "pbc_2651661972", + "indexes": [], + "listRule": null, + "name": "site_visitors", + "system": false, + "type": "base", + "updateRule": null, + "viewRule": null + }); + + return app.save(collection); +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_2651661972"); + + return app.delete(collection); +}) diff --git a/backend/pb_migrations/1777809835_updated_site_visitors.js b/backend/pb_migrations/1777809835_updated_site_visitors.js new file mode 100644 index 0000000..99574c5 --- /dev/null +++ b/backend/pb_migrations/1777809835_updated_site_visitors.js @@ -0,0 +1,28 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_2651661972") + + // update collection data + unmarshal({ + "createRule": "", + "deleteRule": "", + "listRule": "", + "updateRule": "", + "viewRule": "" + }, collection) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_2651661972") + + // update collection data + unmarshal({ + "createRule": null, + "deleteRule": null, + "listRule": null, + "updateRule": null, + "viewRule": null + }, collection) + + return app.save(collection) +}) diff --git a/bun.lockb b/bun.lockb new file mode 100644 index 0000000000000000000000000000000000000000..393c51d846056cc812d431fdf8c96a9456e732b4 GIT binary patch literal 91616 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 literal 0 HcmV?d00001 diff --git a/frontend/src/components/base/VisitorCounter.astro b/frontend/src/components/base/VisitorCounter.astro new file mode 100644 index 0000000..a9af2f2 --- /dev/null +++ b/frontend/src/components/base/VisitorCounter.astro @@ -0,0 +1,77 @@ +--- +import { Icon } from 'astro-icon/components'; + +interface Props { + today?: number; + total?: number; +} + +const { today = 0, total = 0 } = Astro.props; +--- + +
+
+ + {today} сегодня +
+
+ + {total} всего +
+
+ + + + \ No newline at end of file diff --git a/frontend/src/components/layouts/footer/Footer.astro b/frontend/src/components/layouts/footer/Footer.astro index d10a3f8..8b275c1 100644 --- a/frontend/src/components/layouts/footer/Footer.astro +++ b/frontend/src/components/layouts/footer/Footer.astro @@ -1,6 +1,7 @@ --- import { CONTACT_CONSTANTS } from "@constants/constants.ts"; import SocialIcons from "@components/base/SocialIcons.astro"; +import VisitorCounter from "@components/base/VisitorCounter.astro"; const currentYear = new Date().getFullYear(); @@ -147,11 +148,14 @@ const menu = [
-

- © {currentYear} ADVOKAT086. Все права защищены. -

+
+

+ © {currentYear} ADVOKAT086. Все права защищены. +

+ +
-
+
+ + + + + \ No newline at end of file diff --git a/frontend/src/icons/users-total.svg b/frontend/src/icons/users-total.svg new file mode 100644 index 0000000..19320c8 --- /dev/null +++ b/frontend/src/icons/users-total.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/pages/api/visitors.ts b/frontend/src/pages/api/visitors.ts new file mode 100644 index 0000000..7aea6f2 --- /dev/null +++ b/frontend/src/pages/api/visitors.ts @@ -0,0 +1,117 @@ +import type { APIRoute } from 'astro'; +import crypto from 'crypto'; + +const POCKETBASE_URL = import.meta.env.POCKETBASE_URL || 'http://localhost:8090'; + +function getClientIp(request: Request): string { + const forwarded = request.headers.get('x-forwarded-for'); + if (forwarded) { + return forwarded.split(',')[0].trim(); + } + const realIp = request.headers.get('x-real-ip'); + if (realIp) { + return realIp; + } + const host = request.headers.get('host') || 'localhost'; + return host.includes('localhost') || host.includes('127.0.0.1') ? 'localhost' : 'unknown'; +} + +function generateVisitorHash(ip: string, userAgent: string): string { + const stableIP = ip.split(':').pop() || ip; + const uaParts = userAgent.split(' '); + const stableUA = uaParts.slice(0, 2).join(' '); + return crypto.createHash('sha256').update(stableIP + stableUA).digest('hex').slice(0, 32); +} + +async function pbRequest(method: string, path: string, body?: object) { + const url = `${POCKETBASE_URL}${path}`; + const options: RequestInit = { + method, + headers: { + 'Content-Type': 'application/json', + 'Origin': 'http://127.0.0.1:4321', + }, + }; + if (body) options.body = JSON.stringify(body); + + const res = await fetch(url, options); + if (!res.ok) { + const err = await res.text(); + throw new Error(`PB ${method} ${path}: ${res.status} - ${err}`); + } + return res.json(); +} + +function jsonResponse(data: object, status = 200): Response { + return new Response(JSON.stringify(data), { + status, + headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } + }); +} + +export const GET: APIRoute = async ({ request }) => { + try { + const url = new URL(request.url); + const isRepeatVisit = url.searchParams.get('repeat') === 'true'; + + const ip = getClientIp(request); + const userAgent = request.headers.get('user-agent') || 'unknown'; + const visitorHash = generateVisitorHash(ip, userAgent); + + const now = new Date(); + const todayStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 0, 0, 0, 0)); + const todayStartStr = todayStart.toISOString().replace('T', ' ').replace('Z', ''); + + const filterTodayVisitor = `visitor_hash="${visitorHash}" && created >= "${todayStartStr}"`; + + let existingVisitor; + try { + const res = await pbRequest('GET', `/api/collections/site_visitors/records?filter=${encodeURIComponent(filterTodayVisitor)}&perPage=1`); + existingVisitor = { totalItems: res.totalItems || 0 }; + } catch { + existingVisitor = { totalItems: 0 }; + } + + let isNewVisitor = false; + + if (isRepeatVisit) { + isNewVisitor = false; + } + else if (existingVisitor.totalItems === 0) { + isNewVisitor = true; + try { + await pbRequest('POST', '/api/collections/site_visitors/records', { + visitor_hash: visitorHash, + ip: ip, + user_agent: userAgent, + }); + } catch { + // ignore + } + } + + const filterToday = `created >= "${todayStartStr}"`; + let todayCount = 0; + let totalCount = 0; + + try { + const resToday = await pbRequest('GET', `/api/collections/site_visitors/records?filter=${encodeURIComponent(filterToday)}&perPage=1`); + todayCount = resToday.totalItems || 0; + } catch { + todayCount = 0; + } + + try { + const resTotal = await pbRequest('GET', `/api/collections/site_visitors/records?perPage=1`); + totalCount = resTotal.totalItems || 0; + } catch { + totalCount = 0; + } + + return jsonResponse({ todayVisitors: todayCount, totalVisitors: totalCount, isNewVisitor }, 200); + + } catch (error) { + console.error('[Visitors] Error:', error); + return jsonResponse({ error: 'Внутренняя ошибка сервера' }, 500); + } +}; \ No newline at end of file diff --git a/package.json b/package.json index 84e8486..45c69c1 100644 --- a/package.json +++ b/package.json @@ -21,5 +21,8 @@ "concurrently": "^9.2.1", "maildev": "^2.2.1" }, - "private": true + "private": true, + "dependencies": { + "astro-icon": "^1.1.5" + } } \ No newline at end of file