Compare commits

...

3 Commits

Author SHA1 Message Date
AderKonstantin
181595f195 changed Upstash Redis to local Redis-server 2025-05-15 22:18:01 +03:00
AderKonstantin
3c6b449280 Добавил русский в навигацию nav 2025-05-15 21:53:14 +03:00
AderKonstantin
139ac3356d Добавил русский и свое название 2025-05-15 21:51:53 +03:00
11 changed files with 9977 additions and 2325 deletions

3
.env Normal file
View File

@ -0,0 +1,3 @@
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD= # если используется

View File

@ -1,2 +0,0 @@
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=

View File

@ -32,13 +32,13 @@ export const Navigation: React.FC = () => {
href="/projects" href="/projects"
className="duration-200 text-zinc-400 hover:text-zinc-100" className="duration-200 text-zinc-400 hover:text-zinc-100"
> >
Projects Проекты
</Link> </Link>
<Link <Link
href="/contact" href="/contact"
className="duration-200 text-zinc-400 hover:text-zinc-100" className="duration-200 text-zinc-400 hover:text-zinc-100"
> >
Contact Контакты
</Link> </Link>
</div> </div>

View File

@ -3,8 +3,8 @@ import React from "react";
import Particles from "./components/particles"; import Particles from "./components/particles";
const navigation = [ const navigation = [
{ name: "Projects", href: "/projects" }, { name: "Проекты", href: "/projects" },
{ name: "Contact", href: "/contact" }, { name: "Контакты", href: "/contact" },
]; ];
export default function Home() { export default function Home() {
@ -29,7 +29,7 @@ export default function Home() {
quantity={100} quantity={100}
/> />
<h1 className="py-3.5 px-0.5 z-10 text-4xl text-transparent duration-1000 bg-white cursor-default text-edge-outline animate-title font-display sm:text-6xl md:text-9xl whitespace-nowrap bg-clip-text "> <h1 className="py-3.5 px-0.5 z-10 text-4xl text-transparent duration-1000 bg-white cursor-default text-edge-outline animate-title font-display sm:text-6xl md:text-9xl whitespace-nowrap bg-clip-text ">
chronark aderk.tech
</h1> </h1>
<div className="hidden w-screen h-px animate-glow md:block animate-fade-right bg-gradient-to-r from-zinc-300/0 via-zinc-300/50 to-zinc-300/0" /> <div className="hidden w-screen h-px animate-glow md:block animate-fade-right bg-gradient-to-r from-zinc-300/0 via-zinc-300/50 to-zinc-300/0" />

View File

@ -4,7 +4,7 @@ import { Mdx } from "@/app/components/mdx";
import { Header } from "./header"; import { Header } from "./header";
import "./mdx.css"; import "./mdx.css";
import { ReportView } from "./view"; import { ReportView } from "./view";
import { Redis } from "@upstash/redis"; import Redis from "ioredis";
export const revalidate = 60; export const revalidate = 60;
@ -14,7 +14,12 @@ type Props = {
}; };
}; };
const redis = Redis.fromEnv(); // Настройка подключения к локальному Redis
const redis = new Redis({
host: process.env.REDIS_HOST || "localhost",
port: parseInt(process.env.REDIS_PORT || "6379"),
password: process.env.REDIS_PASSWORD,
});
export async function generateStaticParams(): Promise<Props["params"][]> { export async function generateStaticParams(): Promise<Props["params"][]> {
return allProjects return allProjects
@ -32,8 +37,11 @@ export default async function PostPage({ params }: Props) {
notFound(); notFound();
} }
const views = // Получаем и преобразуем значение просмотров
(await redis.get<number>(["pageviews", "projects", slug].join(":"))) ?? 0; const views = parseInt(
(await redis.get(`projects:${slug}:views`)) || "0",
10
);
return ( return (
<div className="bg-zinc-50 min-h-screen"> <div className="bg-zinc-50 min-h-screen">

View File

@ -4,19 +4,24 @@ import { allProjects } from "contentlayer/generated";
import { Navigation } from "../components/nav"; import { Navigation } from "../components/nav";
import { Card } from "../components/card"; import { Card } from "../components/card";
import { Article } from "./article"; import { Article } from "./article";
import { Redis } from "@upstash/redis"; import Redis from "ioredis"; // Заменяем @upstash/redis на ioredis
import { Eye } from "lucide-react"; import { Eye } from "lucide-react";
const redis = Redis.fromEnv(); // Создаем подключение к локальному 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 const revalidate = 60;
export default async function ProjectsPage() { export default async function ProjectsPage() {
const views = ( const viewsEntries = await redis.mget(
await redis.mget<number[]>( ...allProjects.map((p) => `projects:${p.slug}:views`)
...allProjects.map((p) => ["pageviews", "projects", p.slug].join(":")), );
)
).reduce((acc, v, i) => { const views = allProjects.reduce((acc, project, index) => {
acc[allProjects[i].slug] = v ?? 0; acc[project.slug] = parseInt(viewsEntries[index] as string) || 0;
return acc; return acc;
}, {} as Record<string, number>); }, {} as Record<string, number>);

11
content/projects/bog.mdx Normal file
View File

@ -0,0 +1,11 @@
---
title: bitofgame.net
description: bitofgame.net is an open source API Key management solution. It allows you to create, manage and validate API Keys for your users.
date: "2025-07-01"
url: https://unkey.dev
published: true
repository: chronark/unkey
---
Unkey is an open source API Key management solution. It allows you to create, manage and validate API Keys for your users. Its built with security and speed in mind.

6976
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@
"@upstash/redis": "^1.23.3", "@upstash/redis": "^1.23.3",
"contentlayer": "^0.3.4", "contentlayer": "^0.3.4",
"framer-motion": "^10.16.4", "framer-motion": "^10.16.4",
"ioredis": "^5.6.1",
"lucide-react": "^0.284.0", "lucide-react": "^0.284.0",
"markdown-wasm": "^1.2.0", "markdown-wasm": "^1.2.0",
"next": "^13.5.4", "next": "^13.5.4",

View File

@ -1,9 +1,15 @@
import { Redis } from "@upstash/redis"; import Redis from "ioredis";
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
const redis = Redis.fromEnv(); // Настройка подключения к локальному 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 config = { export const config = {
runtime: "edge", runtime: "edge", // Возможно потребуется изменить на "nodejs" если возникнут проблемы
}; };
export default async function incr(req: NextRequest): Promise<NextResponse> { export default async function incr(req: NextRequest): Promise<NextResponse> {
@ -15,33 +21,33 @@ export default async function incr(req: NextRequest): Promise<NextResponse> {
} }
const body = await req.json(); const body = await req.json();
let slug: string | undefined = undefined; const slug = body.slug;
if ("slug" in body) {
slug = body.slug;
}
if (!slug) { if (!slug) {
return new NextResponse("Slug not found", { status: 400 }); return new NextResponse("Slug not found", { status: 400 });
} }
const ip = req.ip; const ip = req.ip;
if (ip) { if (ip) {
// Hash the IP in order to not store it directly in your db. // Хеширование IP-адреса
const buf = await crypto.subtle.digest( const buf = await crypto.subtle.digest(
"SHA-256", "SHA-256",
new TextEncoder().encode(ip), new TextEncoder().encode(ip)
); );
const hash = Array.from(new Uint8Array(buf)) const hash = Array.from(new Uint8Array(buf))
.map((b) => b.toString(16).padStart(2, "0")) .map((b) => b.toString(16).padStart(2, "0"))
.join(""); .join("");
// deduplicate the ip for each slug // Проверка уникальности посещения
const isNew = await redis.set(["deduplicate", hash, slug].join(":"), true, { const key = `deduplicate:${hash}:${slug}`;
nx: true, const isNew = await redis.set(key, "1", "EX", 86400, "NX");
ex: 24 * 60 * 60,
});
if (!isNew) { if (!isNew) {
new NextResponse(null, { status: 202 }); return new NextResponse(null, { status: 202 });
} }
} }
await redis.incr(["pageviews", "projects", slug].join(":"));
// Увеличиваем счетчик просмотров
await redis.incr(`projects:${slug}:views`);
return new NextResponse(null, { status: 202 }); return new NextResponse(null, { status: 202 });
} }

5096
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff