diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3e83e6a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +node_modules +.git +.next +.vscode + +Dockerfile +.dockerignore diff --git a/app/api/views/route.ts b/app/api/views/route.ts new file mode 100644 index 0000000..8564b90 --- /dev/null +++ b/app/api/views/route.ts @@ -0,0 +1,26 @@ +import { NextResponse } from "next/server"; +import { getRedisClient } from "../../../lib/redis"; + +export const dynamic = 'force-dynamic'; // Отключаем кеширование + +export async function GET() { + try { + const redis = getRedisClient(); + const projects = await redis.keys("projects:*:views"); + const viewsEntries = await redis.mget(...projects); + + const viewsData = projects.reduce((acc, key, index) => { + const slug = key.split(":")[1]; + acc[slug] = parseInt(viewsEntries[index] as string) || 0; + return acc; + }, {} as Record); + + return NextResponse.json(viewsData); + } catch (error) { + console.error("Redis error:", error); + return NextResponse.json( + { error: "Failed to fetch views" }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 31ab8e2..c8a513e 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -16,13 +16,6 @@ export const metadata: Metadata = { "Junior Dev & Student", url: "https://aderk.tech", siteName: "aderk.tech", - images: [ - { - url: "https://aderk.tech/og.png", - width: 1920, - height: 1080, - }, - ], locale: "ru-RU", type: "website", }, diff --git a/app/projects/[slug]/header.tsx b/app/projects/[slug]/header.tsx index bab697a..04267bb 100644 --- a/app/projects/[slug]/header.tsx +++ b/app/projects/[slug]/header.tsx @@ -10,7 +10,6 @@ type Props = { description: string; repository?: string; }; - views: number; }; export const Header: React.FC = ({ project, views }) => { diff --git a/app/projects/page.tsx b/app/projects/page.tsx index ed06a57..54cbdd4 100644 --- a/app/projects/page.tsx +++ b/app/projects/page.tsx @@ -1,33 +1,44 @@ +'use client'; // Превращаем компонент в клиентский + import Link from "next/link"; -import React from "react"; +import React, { useEffect, useState } from "react"; import { allProjects } from "contentlayer/generated"; import { Navigation } from "../components/nav"; import { Card } from "../components/card"; import { Article } from "./article"; -import Redis from "ioredis"; // Заменяем @upstash/redis на ioredis import { Eye } from "lucide-react"; -// Создаем подключение к локальному Redis -const redis = new Redis({ - host: process.env.REDIS_HOST || "localhost", - port: parseInt(process.env.REDIS_PORT || "6379"), - password: process.env.REDIS_PASSWORD, // если есть пароль -}); - export const revalidate = 60; -export default async function ProjectsPage() { - const viewsEntries = await redis.mget( - ...allProjects.map((p) => `projects:${p.slug}:views`) - ); - const views = allProjects.reduce((acc, project, index) => { - acc[project.slug] = parseInt(viewsEntries[index] as string) || 0; - return acc; - }, {} as Record); +type ViewsData = Record; + +export default function ProjectsPage() { + const [views, setViews] = useState({}); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // Загружаем данные через клиентский запрос + useEffect(() => { + const fetchViews = async () => { + try { + const response = await fetch('/api/views'); + if (!response.ok) throw new Error('Failed to fetch views'); + const data = await response.json(); + setViews(data); + } catch (err) { + setError(err instanceof Error ? err.message : 'Unknown error'); + } finally { + setLoading(false); + } + }; + + fetchViews(); + }, []); const featured = allProjects.find((project) => project.slug === "cbg")!; const top2 = allProjects.find((project) => project.slug === "blog")!; const top3 = allProjects.find((project) => project.slug === "bimkaspace")!; + const sorted = allProjects .filter((p) => p.published) .filter( diff --git a/content/projects/bimkaspace.mdx b/content/projects/bimkaspace.mdx index 7d1af4d..7c2a5bc 100644 --- a/content/projects/bimkaspace.mdx +++ b/content/projects/bimkaspace.mdx @@ -20,4 +20,5 @@ published: true - [Gin](https://gin-gonic.com/) ### DataBase -- PostgreSQL \ No newline at end of file +- PostgreSQL + diff --git a/docker-compose.yml b/docker-compose.yml index 0274e8b..9c95914 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,26 +1,7 @@ services: - next: - build: - context: . - dockerfile: Dockerfile - container_name: example-frontend - labels: - - "traefik.enable=true" - - "traefik.http.routers.next.rule=Host(`beta.example`)" - - "traefik.http.routers.next.entrypoints=https" # Thats correct - - "traefik.http.routers.next.tls=true" # I dont need certresolver here - - "traefik.http.services.next.loadbalancer.server.port=3000" - environment: - - REDIS_HOST=redis - - REDIS_PORT=6379 - - REDIS_PASSWORD=${REDIS_PASSWORD} - depends_on: - - redis - networks: - - proxy - - backend redis: image: redis:alpine + container_name: main-aderk-redis volumes: - redis_data:/data environment: @@ -34,6 +15,26 @@ services: interval: 5s timeout: 3s retries: 3 + next: + build: + context: . + dockerfile: Dockerfile + container_name: main-aderk-next + labels: + - "traefik.enable=true" + - "traefik.http.routers.next.rule=Host(`aderk.tech`)" + - "traefik.http.routers.next.entrypoints=https" + - "traefik.http.routers.next.tls=true" + - "traefik.http.services.next.loadbalancer.server.port=3000" + environment: + - REDIS_HOST=redis + - REDIS_PORT=6379 + - REDIS_PASSWORD=${REDIS_PASSWORD} + depends_on: + - redis + networks: + - proxy + - backend volumes: redis_data: diff --git a/lib/redis.ts b/lib/redis.ts new file mode 100644 index 0000000..42b262f --- /dev/null +++ b/lib/redis.ts @@ -0,0 +1,18 @@ +import Redis from "ioredis"; + +let redis: Redis | null = null; + +export const getRedisClient = () => { + if (!redis) { + redis = new Redis({ + host: process.env.REDIS_HOST || "localhost", + port: parseInt(process.env.REDIS_PORT || "6379"), + password: process.env.REDIS_PASSWORD, + }); + + redis.on("error", (err) => { + console.error("Redis error:", err); + }); + } + return redis; +}; \ No newline at end of file diff --git a/pages/api/incr.ts b/pages/api/incr.ts index 9fea379..7ef4f64 100644 --- a/pages/api/incr.ts +++ b/pages/api/incr.ts @@ -9,7 +9,7 @@ const redis = new Redis({ }); export const config = { - runtime: "edge", // Возможно потребуется изменить на "nodejs" если возникнут проблемы + runtime: "nodejs", // Возможно потребуется изменить на "nodejs" если возникнут проблемы }; export default async function incr(req: NextRequest): Promise {