add otp swagger1

This commit is contained in:
2026-05-11 14:53:55 +03:30
parent 22d8b15000
commit e60401a86c
3 changed files with 193 additions and 7 deletions

View File

@@ -0,0 +1,171 @@
import { revalidatePath } from "next/cache";
import { db } from "@/lib/db";
import { requireAdmin } from "@/lib/session";
function toDateTimeLocalValue(date: Date) {
const offsetMs = date.getTimezoneOffset() * 60 * 1000;
return new Date(date.getTime() - offsetMs).toISOString().slice(0, 16);
}
async function createNews(formData: FormData) {
"use server";
await requireAdmin();
const icon = String(formData.get("icon") ?? "").trim();
const title = String(formData.get("title") ?? "").trim();
const description = String(formData.get("description") ?? "").trim();
const newsTimeValue = String(formData.get("newsTime") ?? "").trim();
const newsTime = new Date(newsTimeValue);
if (!icon || !title || !description || Number.isNaN(newsTime.getTime())) {
return;
}
await db.fantasyNews.create({
data: {
icon,
title,
description,
newsTime,
},
});
revalidatePath("/admin/news");
revalidatePath("/");
}
async function deleteNews(formData: FormData) {
"use server";
await requireAdmin();
const id = String(formData.get("id") ?? "");
if (!id) return;
await db.fantasyNews.delete({ where: { id } });
revalidatePath("/admin/news");
revalidatePath("/");
}
export default async function AdminNewsPage() {
await requireAdmin();
const news = await db.fantasyNews.findMany({
orderBy: [{ newsTime: "desc" }, { createdAt: "desc" }],
});
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold">مدیریت اخبار</h1>
<p className="text-sm text-gray-500 mt-1">اخبار فانتزی که در API اخبار نمایش داده میشوند.</p>
</div>
</div>
<form action={createNews} className="bg-white rounded-2xl shadow p-6 grid grid-cols-1 gap-4">
<div className="grid grid-cols-3 gap-4">
<label className="flex flex-col gap-2 text-sm font-medium text-gray-700">
آیکن
<input
name="icon"
required
defaultValue="info"
className="rounded-xl border border-gray-300 px-4 py-2.5 outline-none focus:border-green-700"
placeholder="info"
/>
</label>
<label className="flex flex-col gap-2 text-sm font-medium text-gray-700 col-span-2">
عنوان
<input
name="title"
required
className="rounded-xl border border-gray-300 px-4 py-2.5 outline-none focus:border-green-700"
placeholder="مثلاً خبر ترکیب تیم‌ها"
/>
</label>
</div>
<label className="flex flex-col gap-2 text-sm font-medium text-gray-700">
متن خبر
<textarea
name="description"
required
rows={5}
className="rounded-xl border border-gray-300 px-4 py-3 outline-none focus:border-green-700 resize-y"
placeholder="متن کامل خبر را وارد کنید"
/>
</label>
<div className="flex items-end justify-between gap-4">
<label className="flex flex-col gap-2 text-sm font-medium text-gray-700 w-72">
زمان خبر
<input
name="newsTime"
type="datetime-local"
required
defaultValue={toDateTimeLocalValue(new Date())}
className="rounded-xl border border-gray-300 px-4 py-2.5 outline-none focus:border-green-700"
/>
</label>
<button
type="submit"
className="bg-green-700 text-white px-6 py-2.5 rounded-xl hover:bg-green-800 transition font-medium"
>
ثبت خبر
</button>
</div>
</form>
<div className="bg-white rounded-2xl shadow overflow-hidden">
<table className="w-full text-sm">
<thead className="bg-gray-100 text-gray-600">
<tr>
<th className="text-right px-5 py-4">زمان</th>
<th className="text-right px-5 py-4">عنوان</th>
<th className="text-right px-5 py-4">متن</th>
<th className="px-5 py-4"></th>
</tr>
</thead>
<tbody>
{news.map((item) => (
<tr key={item.id} className="border-t align-top hover:bg-gray-50 transition">
<td className="px-5 py-4 text-gray-500 whitespace-nowrap">
{item.newsTime.toLocaleString("fa-IR")}
</td>
<td className="px-5 py-4 font-medium">
<div className="flex items-center gap-2">
<span className="text-gray-400">{item.icon}</span>
<span>{item.title}</span>
</div>
</td>
<td className="px-5 py-4 text-gray-600 leading-7 max-w-xl">
{item.description}
</td>
<td className="px-5 py-4">
<form action={deleteNews}>
<input type="hidden" name="id" value={item.id} />
<button type="submit" className="text-red-600 hover:underline text-xs">
حذف
</button>
</form>
</td>
</tr>
))}
{news.length === 0 && (
<tr>
<td colSpan={4} className="text-center py-10 text-gray-400">
هنوز خبری ثبت نشده است
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
);
}

View File

@@ -1,5 +1,22 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {};
const nextConfig: NextConfig = {
async headers() {
return [
{
source: "/api/:path*",
headers: [
{ key: "Access-Control-Allow-Origin", value: "*" },
{ key: "Access-Control-Allow-Methods", value: "GET,POST,PUT,PATCH,DELETE,OPTIONS" },
{
key: "Access-Control-Allow-Headers",
value: "Content-Type, Authorization, X-Requested-With, x-news-mode, x-news-summary",
},
{ key: "Access-Control-Max-Age", value: "86400" },
],
},
];
},
};
export default nextConfig;

View File

@@ -1,7 +1,5 @@
import { PrismaClient } from "../lib/generated/prisma";
import bcrypt from "bcryptjs";
const prisma = new PrismaClient();
import { db } from "../lib/db";
async function main() {
console.log("🔧 ساخت کاربر ادمین...\n");
@@ -11,11 +9,11 @@ async function main() {
const hashedPassword = await bcrypt.hash(password, 10);
// حذف کاربر قبلی اگر وجود دارد
await prisma.user.deleteMany({
await db.user.deleteMany({
where: { email },
});
const user = await prisma.user.create({
const user = await db.user.create({
data: {
email,
password: hashedPassword,
@@ -40,5 +38,5 @@ main()
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
await db.$disconnect();
});