Compare commits

..

No commits in common. "ed7168381016eadfd62dacaacded8c5b74e60619" and "4283932d9eabe668b50e9f2d211d97a58480757d" have entirely different histories.

11 changed files with 51 additions and 104 deletions

View File

@ -1,7 +0,0 @@
node_modules
.git
.next
.vscode
Dockerfile
.dockerignore

View File

@ -1,25 +0,0 @@
import { NextResponse } from "next/server";
import { getRedisClient } from "../../../lib/redis";
export const dynamic = 'force-dynamic';
export const fetchCache = 'force-no-store';
export async function GET() {
try {
const redis = getRedisClient();
const keys = await redis.keys('projects:*:views');
if (keys.length === 0) return NextResponse.json({});
const values = await redis.mget(...keys);
const views = keys.reduce((acc, key, i) => {
const slug = key.split(':')[1];
acc[slug] = parseInt(values[i] as string) || 0;
return acc;
}, {} as Record<string, number>);
return NextResponse.json(views);
} catch (error) {
console.error('Redis error:', error);
return NextResponse.json({}, { status: 500 });
}
}

View File

@ -16,6 +16,13 @@ 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",
},
@ -31,7 +38,7 @@ export const metadata: Metadata = {
},
},
icons: {
shortcut: "/favicon.png",
shortcut: "/favicon.jpg",
},
};
const inter = Inter({

View File

@ -10,6 +10,7 @@ type Props = {
description: string;
repository?: string;
};
views: number;
};
export const Header: React.FC<Props> = ({ project, views }) => {

View File

@ -1,41 +1,33 @@
'use client';
import Link from "next/link";
import React, { useEffect, useState } from "react";
import React 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";
type ViewsData = Record<string, number>;
// Создаем подключение к локальному Redis
const redis = new Redis({
host: process.env.REDIS_HOST || "localhost",
port: parseInt(process.env.REDIS_PORT || "6379"),
password: process.env.REDIS_PASSWORD, // если есть пароль
});
export default function ProjectsPage() {
const [views, setViews] = useState<ViewsData>({});
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
export const revalidate = 60;
export default async function ProjectsPage() {
const viewsEntries = await redis.mget(
...allProjects.map((p) => `projects:${p.slug}:views`)
);
useEffect(() => {
const fetchViews = async () => {
try {
const response = await fetch('/api/views');
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
setViews(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
fetchViews();
}, []);
const views = allProjects.reduce((acc, project, index) => {
acc[project.slug] = parseInt(viewsEntries[index] as string) || 0;
return acc;
}, {} as Record<string, number>);
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(

View File

@ -20,5 +20,4 @@ published: true
- [Gin](https://gin-gonic.com/)
### DataBase
- PostgreSQL
- PostgreSQL

View File

@ -1,47 +1,45 @@
services:
redis:
image: redis:alpine
container_name: main-aderk-redis
volumes:
- redis_data:/data
environment:
- REDIS_PASSWORD=${REDIS_PASSWORD}
command: [ "redis-server", "--requirepass", "${REDIS_PASSWORD}" ]
networks:
- backend
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
interval: 5s
timeout: 3s
retries: 3
next:
build:
context: .
dockerfile: Dockerfile
container_name: main-aderk-next
container_name: example-frontend
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.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_HOST}
- REDIS_PORT=${REDIS_PORT}
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD}
depends_on:
- redis
networks:
- proxy
- proxy
- backend
redis:
image: redis:alpine
volumes:
- redis_data:/data
environment:
- REDIS_PASSWORD=${REDIS_PASSWORD}
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
networks:
- backend
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 3
volumes:
redis_data:
networks:
proxy:
external: true
backend:
internal: true
internal: true

View File

@ -1,18 +0,0 @@
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;
};

View File

@ -9,7 +9,7 @@ const redis = new Redis({
});
export const config = {
runtime: "nodejs", // Возможно потребуется изменить на "nodejs" если возникнут проблемы
runtime: "edge", // Возможно потребуется изменить на "nodejs" если возникнут проблемы
};
export default async function incr(req: NextRequest): Promise<NextResponse> {

BIN
public/favicon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 465 KiB