Files
football-next/lib/openapi.ts
2026-05-11 16:06:47 +03:30

1648 lines
60 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const authSecurity = [{ SessionToken: [] }, { SecureSessionToken: [] }, { BearerAuth: [] }];
const adminSecurity = [{ SessionToken: [] }, { SecureSessionToken: [] }, { BearerAuth: [] }];
const jsonContent = (schema: unknown, example?: unknown) => ({
"application/json": {
schema,
...(example !== undefined ? { example } : {}),
},
});
const requestBody = (schema: unknown, example?: unknown, required = true) => ({
required,
content: jsonContent(schema, example),
});
const jsonResponse = (description: string, schema: unknown, example?: unknown) => ({
description,
content: jsonContent(schema, example),
});
const errorResponse = (status: string, description: string, example?: string) => [
status,
jsonResponse(
description,
{ $ref: "#/components/schemas/ErrorResponse" },
example ? { error: example } : undefined
),
];
export const openApiSpec = {
openapi: "3.0.3",
info: {
title: "Football Next API",
version: "1.0.0",
description:
"مستندات Swagger/OpenAPI برای تمام APIهای پروژه Football Next. این مستندات بر اساس Routeهای فعلی پروژه تهیه شده و برای توسعه، تست و تحویل به فرانت/بک قابل استفاده است.",
},
servers: [
{
url: process.env.NEXTAUTH_URL ?? "http://localhost:3000",
description: "Current app origin",
},
],
tags: [
{ name: "Auth", description: "ثبت‌نام، نشست و مسیرهای مرتبط با NextAuth" },
{ name: "User", description: "عملیات مرتبط با پروفایل و نشست کاربر" },
{ name: "Team", description: "ساخت و مدیریت تیم فانتزی کاربر" },
{ name: "Players", description: "دریافت و مدیریت بازیکنان" },
{ name: "Countries", description: "دریافت و مدیریت کشورها" },
{ name: "Matches", description: "دریافت و مدیریت بازی‌ها و آمار آن‌ها" },
{ name: "Rounds", description: "مدیریت دورها" },
{ name: "Gameweeks", description: "مدیریت هفته‌ها" },
{ name: "Leaderboard", description: "رتبه‌بندی تیم‌ها" },
{ name: "Upload", description: "آپلود فایل‌های تصویری" },
{ name: "Payment", description: "شروع و تایید پرداخت" },
{ name: "Quiz", description: "کوئیز روزانه و نتایج آن" },
{ name: "Golden Cards", description: "کارت‌های طلایی کاربر" },
{ name: "Admin", description: "APIهای ویژه ادمین" },
],
components: {
securitySchemes: {
SessionToken: {
type: "apiKey",
in: "cookie",
name: "next-auth.session-token",
description: "توکن سشن NextAuth در محیط عادی",
},
SecureSessionToken: {
type: "apiKey",
in: "cookie",
name: "__Secure-next-auth.session-token",
description: "توکن سشن NextAuth در محیط HTTPS",
},
BearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "opaque",
description: "Mobile app access token returned by /api/auth/mobile/verify or /api/auth/mobile/refresh",
},
},
schemas: {
ErrorResponse: {
type: "object",
properties: {
error: { type: "string", example: "Unauthorized" },
},
required: ["error"],
},
SuccessResponse: {
type: "object",
properties: {
success: { type: "boolean", example: true },
},
required: ["success"],
},
Role: {
type: "string",
enum: ["USER", "ADMIN"],
},
Position: {
type: "string",
enum: ["GK", "DEF", "MID", "FWD"],
},
MatchStage: {
type: "string",
enum: ["GROUP", "ROUND_OF_16", "QUARTER_FINAL", "SEMI_FINAL", "THIRD_PLACE", "FINAL"],
},
MatchStatus: {
type: "string",
enum: ["SCHEDULED", "LIVE", "FINISHED"],
},
TeamStatus: {
type: "string",
enum: ["PENDING", "APPROVED", "REJECTED", "ACTIVE", "INACTIVE"],
},
GoldenCardStatus: {
type: "string",
enum: ["SEALED", "OPENED"],
},
EventType: {
type: "string",
enum: [
"GOAL",
"ASSIST",
"YELLOW_CARD",
"RED_CARD",
"SECOND_YELLOW",
"SUBSTITUTION_IN",
"SUBSTITUTION_OUT",
"INJURY_NO_SUB",
"CLEAN_SHEET",
"PENALTY_SAVED",
"PENALTY_MISSED",
"OWN_GOAL",
"EXTRA_TIME_BONUS",
"MOTM",
],
},
Group: {
type: "object",
properties: {
id: { type: "string" },
name: { type: "string" },
},
},
Country: {
type: "object",
properties: {
id: { type: "string" },
name: { type: "string" },
code: { type: "string" },
flagUrl: { type: "string", nullable: true },
flagImage: { type: "string", nullable: true },
confederation: { type: "string", nullable: true },
qualificationMethod: { type: "string", nullable: true },
qualificationDate: { type: "string", nullable: true },
participationHistory: { type: "string", nullable: true },
bestResult: { type: "string", nullable: true },
description: { type: "string", nullable: true },
defaultFormation: { type: "string", example: "4-3-3" },
defaultLineupPlayerIds: { type: "array", items: { type: "string" } },
defaultCaptainId: { type: "string", nullable: true },
groupId: { type: "string", nullable: true },
isEliminated: { type: "boolean" },
group: { $ref: "#/components/schemas/Group" },
},
},
Player: {
type: "object",
properties: {
id: { type: "string" },
name: { type: "string" },
image: { type: "string", nullable: true },
position: { $ref: "#/components/schemas/Position" },
countryId: { type: "string" },
country: { $ref: "#/components/schemas/Country" },
price: { type: "number", format: "float" },
totalPoints: { type: "integer" },
isActive: { type: "boolean" },
isGoldenCardEligible: { type: "boolean" },
createdAt: { type: "string", format: "date-time" },
updatedAt: { type: "string", format: "date-time" },
},
},
MatchLineupInput: {
type: "object",
properties: {
countryId: { type: "string" },
formation: { type: "string", example: "4-3-3" },
playerIds: { type: "array", items: { type: "string" } },
},
required: ["countryId", "formation", "playerIds"],
},
MatchEvent: {
type: "object",
properties: {
id: { type: "string" },
matchId: { type: "string" },
playerId: { type: "string" },
type: { $ref: "#/components/schemas/EventType" },
minute: { type: "integer", nullable: true },
extraInfo: { type: "string", nullable: true },
createdAt: { type: "string", format: "date-time" },
},
},
PlayerMatchStat: {
type: "object",
properties: {
id: { type: "string" },
playerId: { type: "string" },
matchId: { type: "string" },
goals: { type: "integer" },
assists: { type: "integer" },
yellowCards: { type: "integer" },
redCards: { type: "integer" },
minutesPlayed: { type: "integer" },
cleanSheet: { type: "boolean" },
penaltySaved: { type: "integer" },
penaltyMissed: { type: "integer" },
ownGoals: { type: "integer" },
isMotm: { type: "boolean" },
extraTimeBonus: { type: "integer" },
points: { type: "integer" },
},
},
Match: {
type: "object",
properties: {
id: { type: "string" },
homeTeamId: { type: "string" },
awayTeamId: { type: "string" },
homeTeam: { $ref: "#/components/schemas/Country" },
awayTeam: { $ref: "#/components/schemas/Country" },
homeScore: { type: "integer", nullable: true },
awayScore: { type: "integer", nullable: true },
stage: { $ref: "#/components/schemas/MatchStage" },
status: { $ref: "#/components/schemas/MatchStatus" },
matchDate: { type: "string", format: "date-time" },
matchDatePersian: { type: "string", nullable: true },
stadium: { type: "string", nullable: true },
city: { type: "string", nullable: true },
referee: { type: "string", nullable: true },
assistant1: { type: "string", nullable: true },
assistant2: { type: "string", nullable: true },
fourthOfficial: { type: "string", nullable: true },
attendance: { type: "integer", nullable: true },
weather: { type: "string", nullable: true },
description: { type: "string", nullable: true },
roundId: { type: "string", nullable: true },
playerStats: {
type: "array",
items: { $ref: "#/components/schemas/PlayerMatchStat" },
},
createdAt: { type: "string", format: "date-time" },
},
},
Round: {
type: "object",
properties: {
id: { type: "string" },
number: { type: "integer" },
name: { type: "string" },
isActive: { type: "boolean" },
deadline: { type: "string", format: "date-time" },
createdAt: { type: "string", format: "date-time" },
},
},
Gameweek: {
type: "object",
properties: {
id: { type: "string" },
number: { type: "integer" },
name: { type: "string" },
isActive: { type: "boolean" },
deadline: { type: "string", format: "date-time" },
createdAt: { type: "string", format: "date-time" },
},
},
TeamPlayer: {
type: "object",
properties: {
teamId: { type: "string" },
playerId: { type: "string" },
player: { $ref: "#/components/schemas/Player" },
isCaptain: { type: "boolean" },
isViceCaptain: { type: "boolean" },
isBench: { type: "boolean" },
positionIndex: { type: "integer" },
},
},
Team: {
type: "object",
properties: {
id: { type: "string" },
name: { type: "string" },
userId: { type: "string" },
budget: { type: "number", format: "float" },
totalPoints: { type: "integer" },
formation: { type: "string" },
status: { $ref: "#/components/schemas/TeamStatus" },
createdAt: { type: "string", format: "date-time" },
players: {
type: "array",
items: { $ref: "#/components/schemas/TeamPlayer" },
},
},
},
LeaderboardEntry: {
type: "object",
properties: {
rank: { type: "integer" },
teamName: { type: "string" },
userName: { type: "string" },
totalPoints: { type: "integer" },
budget: { type: "number", format: "float" },
},
},
FantasyNews: {
type: "object",
properties: {
id: { type: "string" },
icon: { type: "string" },
title: { type: "string" },
description: { type: "string" },
newsTime: { type: "string", format: "date-time" },
createdAt: { type: "string", format: "date-time" },
updatedAt: { type: "string", format: "date-time", nullable: true },
},
},
FantasyNewsListResponse: {
type: "object",
properties: {
data: {
type: "array",
items: { $ref: "#/components/schemas/FantasyNews" },
},
},
},
FantasyNewsCreateRequest: {
type: "object",
properties: {
icon: { type: "string", example: "info" },
title: { type: "string", example: "Lineup update" },
description: { type: "string", example: "Confirmed team news before kickoff." },
newsTime: { type: "string", format: "date-time", example: "2026-06-10T12:00:00.000Z" },
},
required: ["icon", "title", "description", "newsTime"],
},
PaymentRequestPayload: {
type: "object",
properties: {
packageId: { type: "string" },
},
required: ["packageId"],
},
PaymentRequestResponse: {
type: "object",
properties: {
paymentUrl: { type: "string", format: "uri" },
},
},
UploadPlayerImageResponse: {
type: "object",
properties: {
success: { type: "boolean" },
fileName: { type: "string" },
url: { type: "string" },
},
},
SessionInfo: {
type: "object",
properties: {
user: {
type: "object",
properties: {
name: { type: "string", nullable: true },
email: { type: "string", nullable: true },
image: { type: "string", nullable: true },
role: { $ref: "#/components/schemas/Role" },
id: { type: "string" },
},
},
expires: { type: "string", format: "date-time" },
},
},
DailyQuizQuestionPublic: {
type: "object",
properties: {
id: { type: "string" },
questionText: { type: "string" },
options: { type: "array", items: { type: "string" } },
order: { type: "integer" },
},
},
DailyQuiz: {
type: "object",
properties: {
id: { type: "string" },
date: { type: "string", format: "date" },
windowStart: { type: "string", format: "date-time" },
windowEnd: { type: "string", format: "date-time" },
goldWinnersCount: { type: "integer" },
silverWinnersCount: { type: "integer" },
bronzeWinnersCount: { type: "integer" },
isProcessed: { type: "boolean" },
createdAt: { type: "string", format: "date-time" },
isActive: { type: "boolean" },
questions: {
type: "array",
items: { $ref: "#/components/schemas/DailyQuizQuestionPublic" },
},
},
},
GoldenCard: {
type: "object",
properties: {
id: { type: "string" },
userId: { type: "string" },
playerId: { type: "string" },
status: { $ref: "#/components/schemas/GoldenCardStatus" },
acquiredDate: { type: "string", format: "date-time" },
openedAt: { type: "string", format: "date-time", nullable: true },
player: { $ref: "#/components/schemas/Player" },
},
},
RegisterRequest: {
type: "object",
properties: {
name: { type: "string", nullable: true },
email: { type: "string", format: "email" },
password: { type: "string", format: "password" },
},
required: ["email", "password"],
},
RegisterResponse: {
type: "object",
properties: {
id: { type: "string" },
},
},
MobileOtpRequestCodeRequest: {
type: "object",
properties: {
phone: { type: "string", example: "09123456789" },
},
required: ["phone"],
},
MobileOtpRequestCodeResponse: {
type: "object",
properties: {
ok: { type: "boolean", example: true },
expiresIn: { type: "integer", example: 120 },
},
},
MobileOtpVerifyRequest: {
type: "object",
properties: {
phone: { type: "string", example: "09123456789" },
code: { type: "string", example: "123456" },
name: { type: "string", nullable: true, example: "Ali" },
},
required: ["phone", "code"],
},
MobileOtpVerifyResponse: {
type: "object",
properties: {
accessToken: { type: "string" },
token: { type: "string" },
tokenType: { type: "string", example: "Bearer" },
expiresIn: { type: "integer", example: 900 },
expiresAt: { type: "string", format: "date-time" },
refreshToken: { type: "string" },
refreshExpiresIn: { type: "integer", example: 2592000 },
refreshExpiresAt: { type: "string", format: "date-time" },
user: {
type: "object",
properties: {
id: { type: "string" },
name: { type: "string", nullable: true },
phone: { type: "string", nullable: true },
role: { $ref: "#/components/schemas/Role" },
},
},
},
},
MobileRefreshRequest: {
type: "object",
properties: {
refreshToken: { type: "string" },
},
required: ["refreshToken"],
},
MobileLogoutRequest: {
type: "object",
properties: {
refreshToken: { type: "string" },
},
},
CredentialsLoginRequest: {
type: "object",
properties: {
email: { type: "string", format: "email" },
password: { type: "string", format: "password" },
redirect: { type: "boolean", default: false },
callbackUrl: { type: "string", nullable: true },
},
required: ["email", "password"],
},
TeamCreateRequest: {
type: "object",
properties: {
name: { type: "string" },
formation: { type: "string", example: "4-3-3" },
},
required: ["name"],
},
TeamPlayerAddRequest: {
type: "object",
properties: {
playerId: { type: "string" },
isBench: { type: "boolean", default: false },
},
required: ["playerId"],
},
TeamPlayerRemoveRequest: {
type: "object",
properties: {
playerId: { type: "string" },
},
required: ["playerId"],
},
TeamFormationRequest: {
type: "object",
properties: {
formation: {
type: "string",
enum: ["4-3-3", "4-4-2", "4-5-1", "3-5-2", "3-4-3", "5-3-2", "5-4-1"],
},
},
required: ["formation"],
},
TeamCaptainRequest: {
type: "object",
properties: {
playerId: { type: "string" },
type: { type: "string", enum: ["captain", "viceCaptain"] },
},
required: ["playerId", "type"],
},
ProfileUpdateRequest: {
type: "object",
properties: {
name: { type: "string" },
},
required: ["name"],
},
ManualPlayerStatInput: {
type: "object",
properties: {
playerId: { type: "string" },
goals: { type: "integer" },
assists: { type: "integer" },
yellowCards: { type: "integer" },
redCards: { type: "integer" },
minutesPlayed: { type: "integer" },
cleanSheet: { type: "boolean" },
},
required: ["playerId", "goals", "assists", "yellowCards", "redCards", "minutesPlayed", "cleanSheet"],
},
QuizSubmitRequest: {
type: "object",
properties: {
quizId: { type: "string" },
answers: { type: "array", items: { type: "integer" } },
},
required: ["quizId", "answers"],
},
AdminQuizCreateRequest: {
type: "object",
properties: {
date: { type: "string", format: "date" },
windowStart: { type: "string", format: "date-time" },
windowEnd: { type: "string", format: "date-time" },
goldWinnersCount: { type: "integer", default: 1 },
silverWinnersCount: { type: "integer", default: 0 },
bronzeWinnersCount: { type: "integer", default: 0 },
questions: {
type: "array",
items: {
type: "object",
properties: {
questionText: { type: "string" },
options: { type: "array", items: { type: "string" } },
correctAnswer: { type: "integer" },
},
required: ["questionText", "options", "correctAnswer"],
},
},
},
required: ["date", "windowStart", "windowEnd", "goldWinnersCount", "silverWinnersCount", "bronzeWinnersCount", "questions"],
},
AdminTeamStatusUpdateRequest: {
type: "object",
properties: {
status: { $ref: "#/components/schemas/TeamStatus" },
},
required: ["status"],
},
ScoringRuleInput: {
type: "object",
properties: {
position: { $ref: "#/components/schemas/Position" },
eventType: { $ref: "#/components/schemas/EventType" },
points: { type: "integer" },
},
required: ["position", "eventType", "points"],
},
MatchEventCreateRequest: {
type: "object",
properties: {
playerId: { type: "string" },
type: { $ref: "#/components/schemas/EventType" },
minute: { type: "integer", nullable: true },
extraInfo: { type: "string", nullable: true },
},
required: ["playerId", "type"],
},
},
},
paths: {
"/api/auth/register": {
post: {
tags: ["Auth"],
summary: "ثبت‌نام کاربر جدید",
requestBody: requestBody(
{ $ref: "#/components/schemas/RegisterRequest" },
{ name: "Ali", email: "ali@example.com", password: "123456" }
),
responses: Object.fromEntries([
["201", jsonResponse("کاربر ساخته شد", { $ref: "#/components/schemas/RegisterResponse" }, { id: "clx123" })],
errorResponse("400", "ورودی نامعتبر", "این ایمیل قبلاً ثبت شده"),
]),
},
},
"/api/auth/mobile/request-code": {
post: {
tags: ["Auth"],
summary: "Request mobile OTP login code",
requestBody: requestBody(
{ $ref: "#/components/schemas/MobileOtpRequestCodeRequest" },
{ phone: "09123456789" }
),
responses: Object.fromEntries([
["200", jsonResponse("OTP code sent", { $ref: "#/components/schemas/MobileOtpRequestCodeResponse" })],
errorResponse("400", "Invalid mobile phone", "Invalid mobile phone"),
errorResponse("429", "OTP request rate limited", "Please wait before requesting a new code"),
errorResponse("502", "SMS provider failed", "SMS send failed"),
]),
},
},
"/api/auth/mobile/verify": {
post: {
tags: ["Auth"],
summary: "Verify mobile OTP login code",
requestBody: requestBody(
{ $ref: "#/components/schemas/MobileOtpVerifyRequest" },
{ phone: "09123456789", code: "123456", name: "Ali" }
),
responses: Object.fromEntries([
["200", jsonResponse("OTP verified", { $ref: "#/components/schemas/MobileOtpVerifyResponse" })],
errorResponse("400", "Invalid or expired OTP", "Invalid or expired OTP"),
]),
},
},
"/api/auth/mobile/refresh": {
post: {
tags: ["Auth"],
summary: "Rotate refresh token and issue a new mobile access token",
requestBody: requestBody(
{ $ref: "#/components/schemas/MobileRefreshRequest" },
{ refreshToken: "refresh_token_value" }
),
responses: Object.fromEntries([
["200", jsonResponse("Token refreshed", { $ref: "#/components/schemas/MobileOtpVerifyResponse" })],
errorResponse("400", "Missing refresh token", "refreshToken is required"),
errorResponse("401", "Invalid, expired, or revoked refresh token", "Invalid refresh token"),
]),
},
},
"/api/auth/mobile/logout": {
post: {
tags: ["Auth"],
summary: "Revoke mobile refresh token family and delete current access token",
security: authSecurity,
requestBody: requestBody(
{ $ref: "#/components/schemas/MobileLogoutRequest" },
{ refreshToken: "refresh_token_value" },
false
),
responses: {
"200": jsonResponse("Logged out", {
type: "object",
properties: {
ok: { type: "boolean", example: true },
},
}, { ok: true }),
},
},
},
"/api/auth/callback/credentials": {
post: {
tags: ["Auth"],
summary: "ورود با ایمیل و رمز عبور",
description:
"مسیر استاندارد NextAuth برای ورود. در فرانت معمولاً از `signIn('credentials')` استفاده می‌شود. در Swagger برای تست بهتر است از همان مرورگری استفاده شود که Session Cookie را نگه می‌دارد.",
requestBody: requestBody(
{ $ref: "#/components/schemas/CredentialsLoginRequest" },
{ email: "admin@example.com", password: "123456", redirect: false }
),
responses: {
"200": {
description: "ورود موفق یا پاسخ استاندارد NextAuth",
content: {
"application/json": {
schema: {
type: "object",
additionalProperties: true,
},
},
},
},
},
},
},
"/api/auth/session": {
get: {
tags: ["Auth"],
summary: "دریافت سشن جاری",
responses: {
"200": jsonResponse(
"اطلاعات نشست",
{
oneOf: [{ $ref: "#/components/schemas/SessionInfo" }, { type: "null" }],
}
),
},
},
},
"/api/auth/csrf": {
get: {
tags: ["Auth"],
summary: "دریافت CSRF Token برای NextAuth",
responses: {
"200": jsonResponse(
"توکن CSRF",
{
type: "object",
properties: {
csrfToken: { type: "string" },
},
}
),
},
},
},
"/api/auth/signout": {
post: {
tags: ["Auth"],
summary: "خروج کاربر",
security: authSecurity,
responses: {
"200": {
description: "خروج موفق",
},
},
},
},
"/api/user/profile": {
put: {
tags: ["User"],
summary: "ویرایش نام کاربر",
security: authSecurity,
requestBody: requestBody(
{ $ref: "#/components/schemas/ProfileUpdateRequest" },
{ name: "Ali Rezaei" }
),
responses: Object.fromEntries([
["200", jsonResponse("پروفایل به‌روزرسانی شد", { type: "object", properties: { name: { type: "string", nullable: true } } })],
errorResponse("401", "نیازمند ورود", "Unauthorized"),
]),
},
},
"/api/test-session": {
get: {
tags: ["User"],
summary: "بررسی سشن و وضعیت کاربر در دیتابیس",
security: authSecurity,
responses: Object.fromEntries([
[
"200",
jsonResponse("وضعیت سشن", {
type: "object",
additionalProperties: true,
}),
],
errorResponse("401", "بدون سشن", "No session"),
]),
},
},
"/api/team": {
get: {
tags: ["Team"],
summary: "دریافت تیم کاربر جاری",
security: authSecurity,
responses: Object.fromEntries([
["200", jsonResponse("تیم کاربر", { oneOf: [{ $ref: "#/components/schemas/Team" }, { type: "null" }] })],
errorResponse("401", "نیازمند ورود", "Unauthorized"),
]),
},
post: {
tags: ["Team"],
summary: "ساخت تیم جدید برای کاربر",
security: authSecurity,
requestBody: requestBody(
{ $ref: "#/components/schemas/TeamCreateRequest" },
{ name: "Dream FC", formation: "4-3-3" }
),
responses: Object.fromEntries([
["201", jsonResponse("تیم ساخته شد", { $ref: "#/components/schemas/Team" })],
errorResponse("400", "خطای اعتبارسنجی", "Team already exists"),
errorResponse("401", "نیازمند ورود", "Unauthorized"),
errorResponse("404", "کاربر پیدا نشد", "User not found"),
]),
},
},
"/api/team/players": {
post: {
tags: ["Team"],
summary: "افزودن بازیکن به تیم",
security: authSecurity,
requestBody: requestBody(
{ $ref: "#/components/schemas/TeamPlayerAddRequest" },
{ playerId: "player_123", isBench: false }
),
responses: Object.fromEntries([
["201", jsonResponse("بازیکن به تیم اضافه شد", { $ref: "#/components/schemas/TeamPlayer" })],
errorResponse("400", "خطای اعتبارسنجی", "بودجه کافی نیست"),
errorResponse("401", "نیازمند ورود", "Unauthorized"),
errorResponse("404", "بازیکن پیدا نشد", "بازیکن پیدا نشد"),
]),
},
delete: {
tags: ["Team"],
summary: "حذف بازیکن از تیم",
security: authSecurity,
requestBody: requestBody(
{ $ref: "#/components/schemas/TeamPlayerRemoveRequest" },
{ playerId: "player_123" }
),
responses: Object.fromEntries([
["200", jsonResponse("بازیکن حذف شد", { $ref: "#/components/schemas/SuccessResponse" })],
errorResponse("401", "نیازمند ورود", "Unauthorized"),
errorResponse("404", "تیم پیدا نشد", "تیم پیدا نشد"),
]),
},
},
"/api/team/formation": {
put: {
tags: ["Team"],
summary: "تغییر ترکیب تیم",
security: authSecurity,
requestBody: requestBody(
{ $ref: "#/components/schemas/TeamFormationRequest" },
{ formation: "4-4-2" }
),
responses: Object.fromEntries([
["200", jsonResponse("ترکیب به‌روزرسانی شد", { $ref: "#/components/schemas/Team" })],
errorResponse("400", "ترکیب نامعتبر یا ناسازگار", "ترکیب نامعتبر"),
errorResponse("401", "نیازمند ورود", "Unauthorized"),
errorResponse("404", "تیم پیدا نشد", "تیم پیدا نشد"),
]),
},
},
"/api/team/captain": {
put: {
tags: ["Team"],
summary: "تعیین کاپیتان یا نایب کاپیتان",
security: authSecurity,
requestBody: requestBody(
{ $ref: "#/components/schemas/TeamCaptainRequest" },
{ playerId: "player_123", type: "captain" }
),
responses: Object.fromEntries([
["200", jsonResponse("بروزرسانی موفق", { $ref: "#/components/schemas/SuccessResponse" })],
errorResponse("401", "نیازمند ورود", "Unauthorized"),
errorResponse("404", "تیم پیدا نشد", "تیم پیدا نشد"),
]),
},
},
"/api/team/submit": {
post: {
tags: ["Team"],
summary: "ثبت نهایی تیم",
security: authSecurity,
responses: Object.fromEntries([
["200", jsonResponse("تیم نهایی شد", { $ref: "#/components/schemas/Team" })],
errorResponse("400", "ترکیب تیم نامعتبر است", "تیم باید دقیقاً ۱۵ بازیکن داشته باشد"),
errorResponse("401", "نیازمند ورود", "Unauthorized"),
errorResponse("404", "تیم پیدا نشد", "تیم پیدا نشد"),
]),
},
},
"/api/players": {
get: {
tags: ["Players"],
summary: "لیست بازیکنان",
parameters: [
{ in: "query", name: "position", schema: { $ref: "#/components/schemas/Position" }, required: false },
{ in: "query", name: "countryId", schema: { type: "string" }, required: false },
],
responses: {
"200": jsonResponse("لیست بازیکنان", { type: "array", items: { $ref: "#/components/schemas/Player" } }),
},
},
post: {
tags: ["Players"],
summary: "ایجاد بازیکن جدید",
security: adminSecurity,
requestBody: requestBody(
{
type: "object",
additionalProperties: true,
properties: {
name: { type: "string" },
position: { $ref: "#/components/schemas/Position" },
countryId: { type: "string" },
price: { type: "number" },
image: { type: "string", nullable: true },
},
},
{ name: "Lionel Messi", position: "FWD", countryId: "country_1", price: 12.5 }
),
responses: Object.fromEntries([
["201", jsonResponse("بازیکن ایجاد شد", { $ref: "#/components/schemas/Player" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/players/{id}": {
put: {
tags: ["Players"],
summary: "ویرایش بازیکن",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
requestBody: requestBody(
{
type: "object",
additionalProperties: true,
},
{ price: 11.5, isActive: true }
),
responses: Object.fromEntries([
["200", jsonResponse("بازیکن به‌روزرسانی شد", { $ref: "#/components/schemas/Player" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
delete: {
tags: ["Players"],
summary: "حذف بازیکن",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
responses: Object.fromEntries([
["200", jsonResponse("بازیکن حذف شد", { $ref: "#/components/schemas/SuccessResponse" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/admin/players/{id}/golden-toggle": {
patch: {
tags: ["Admin", "Golden Cards"],
summary: "فعال/غیرفعال کردن صلاحیت Golden Card برای بازیکن",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
responses: Object.fromEntries([
[
"200",
jsonResponse("وضعیت تغییر کرد", {
type: "object",
properties: {
isGoldenCardEligible: { type: "boolean" },
},
}),
],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
errorResponse("404", "بازیکن پیدا نشد", "Player not found"),
]),
},
},
"/api/countries": {
get: {
tags: ["Countries"],
summary: "لیست کشورها",
responses: {
"200": jsonResponse("لیست کشورها", { type: "array", items: { $ref: "#/components/schemas/Country" } }),
},
},
post: {
tags: ["Countries"],
summary: "ایجاد کشور جدید",
security: adminSecurity,
requestBody: requestBody(
{
type: "object",
additionalProperties: true,
properties: {
name: { type: "string" },
code: { type: "string" },
defaultFormation: { type: "string" },
},
},
{ name: "Argentina", code: "ARG", defaultFormation: "4-3-3" }
),
responses: Object.fromEntries([
["201", jsonResponse("کشور ایجاد شد", { $ref: "#/components/schemas/Country" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/countries/{id}": {
put: {
tags: ["Countries"],
summary: "ویرایش کشور",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
requestBody: requestBody({ type: "object", additionalProperties: true }, { isEliminated: false }),
responses: Object.fromEntries([
["200", jsonResponse("کشور به‌روزرسانی شد", { $ref: "#/components/schemas/Country" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
delete: {
tags: ["Countries"],
summary: "حذف کشور",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
responses: Object.fromEntries([
["200", jsonResponse("کشور حذف شد", { $ref: "#/components/schemas/SuccessResponse" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/matches": {
get: {
tags: ["Matches"],
summary: "لیست بازی‌ها",
responses: {
"200": jsonResponse("لیست بازی‌ها", { type: "array", items: { $ref: "#/components/schemas/Match" } }),
},
},
post: {
tags: ["Matches"],
summary: "ایجاد بازی جدید",
security: adminSecurity,
requestBody: requestBody(
{
type: "object",
additionalProperties: true,
properties: {
homeTeamId: { type: "string" },
awayTeamId: { type: "string" },
stage: { $ref: "#/components/schemas/MatchStage" },
status: { $ref: "#/components/schemas/MatchStatus" },
matchDate: { type: "string", format: "date-time" },
roundId: { type: "string", nullable: true },
},
},
{
homeTeamId: "country_1",
awayTeamId: "country_2",
stage: "GROUP",
status: "SCHEDULED",
matchDate: "2026-06-10T19:00:00.000Z",
}
),
responses: Object.fromEntries([
["201", jsonResponse("بازی ایجاد شد", { $ref: "#/components/schemas/Match" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/matches/{id}": {
get: {
tags: ["Matches"],
summary: "جزئیات یک بازی",
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
responses: Object.fromEntries([
["200", jsonResponse("جزئیات بازی", { $ref: "#/components/schemas/Match" })],
errorResponse("404", "بازی پیدا نشد", "Not found"),
]),
},
put: {
tags: ["Matches"],
summary: "ویرایش بازی",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
requestBody: requestBody({ type: "object", additionalProperties: true }, { status: "LIVE", homeScore: 1, awayScore: 0 }),
responses: Object.fromEntries([
["200", jsonResponse("بازی به‌روزرسانی شد", { $ref: "#/components/schemas/Match" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
delete: {
tags: ["Matches"],
summary: "حذف بازی",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
responses: Object.fromEntries([
["200", jsonResponse("بازی حذف شد", { $ref: "#/components/schemas/SuccessResponse" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/matches/{id}/stats": {
post: {
tags: ["Matches"],
summary: "ثبت دستی آمار بازیکنان یک بازی",
description: "این مسیر برای ثبت مستقیم آمار بازیکنان و محاسبه امتیاز آن‌ها استفاده می‌شود.",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
requestBody: requestBody(
{
type: "array",
items: { $ref: "#/components/schemas/ManualPlayerStatInput" },
},
[
{
playerId: "player_1",
goals: 1,
assists: 0,
yellowCards: 0,
redCards: 0,
minutesPlayed: 90,
cleanSheet: false,
},
]
),
responses: Object.fromEntries([
["200", jsonResponse("آمار ذخیره شد", { type: "array", items: { $ref: "#/components/schemas/PlayerMatchStat" } })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/admin/matches/{id}/lineup": {
post: {
tags: ["Admin", "Matches"],
summary: "ثبت ترکیب دو تیم برای یک بازی",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
requestBody: requestBody(
{
type: "array",
items: { $ref: "#/components/schemas/MatchLineupInput" },
},
[
{ countryId: "country_1", formation: "4-3-3", playerIds: ["p1", "p2"] },
{ countryId: "country_2", formation: "4-4-2", playerIds: ["p3", "p4"] },
]
),
responses: Object.fromEntries([
["200", jsonResponse("ترکیب‌ها ذخیره شدند", { $ref: "#/components/schemas/SuccessResponse" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/admin/matches/{id}/events": {
post: {
tags: ["Admin", "Matches"],
summary: "ثبت رویداد بازی",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
requestBody: requestBody(
{ $ref: "#/components/schemas/MatchEventCreateRequest" },
{ playerId: "player_1", type: "GOAL", minute: 35, extraInfo: "Right foot" }
),
responses: Object.fromEntries([
["201", jsonResponse("رویداد ذخیره شد", { $ref: "#/components/schemas/MatchEvent" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/admin/matches/{id}/events/{eventId}": {
delete: {
tags: ["Admin", "Matches"],
summary: "حذف رویداد بازی",
security: adminSecurity,
parameters: [
{ in: "path", name: "id", required: true, schema: { type: "string" } },
{ in: "path", name: "eventId", required: true, schema: { type: "string" } },
],
responses: Object.fromEntries([
["200", jsonResponse("رویداد حذف شد", { $ref: "#/components/schemas/SuccessResponse" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/admin/matches/{id}/calc-points": {
post: {
tags: ["Admin", "Matches"],
summary: "محاسبه امتیاز بازیکنان و تیم‌ها از روی رویدادهای ثبت‌شده",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
responses: Object.fromEntries([
[
"200",
jsonResponse("محاسبه انجام شد", {
type: "object",
properties: {
calculated: { type: "integer" },
},
}),
],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/rounds": {
get: {
tags: ["Rounds"],
summary: "لیست دورها",
responses: {
"200": jsonResponse("لیست دورها", { type: "array", items: { $ref: "#/components/schemas/Round" } }),
},
},
post: {
tags: ["Rounds"],
summary: "ایجاد دور جدید",
security: adminSecurity,
requestBody: requestBody(
{
type: "object",
properties: {
number: { type: "integer" },
name: { type: "string" },
deadline: { type: "string", format: "date-time" },
},
required: ["number", "name", "deadline"],
},
{ number: 1, name: "Round 1", deadline: "2026-06-10T12:00:00.000Z" }
),
responses: Object.fromEntries([
["201", jsonResponse("دور ایجاد شد", { $ref: "#/components/schemas/Round" })],
errorResponse("400", "شماره دور تکراری است", "این شماره دور قبلاً ثبت شده"),
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
put: {
tags: ["Rounds"],
summary: "ویرایش دور",
security: adminSecurity,
requestBody: requestBody(
{
type: "object",
properties: {
id: { type: "string" },
number: { type: "integer" },
name: { type: "string" },
deadline: { type: "string", format: "date-time" },
},
required: ["id", "number", "name", "deadline"],
}
),
responses: Object.fromEntries([
["200", jsonResponse("دور به‌روزرسانی شد", { $ref: "#/components/schemas/Round" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
delete: {
tags: ["Rounds"],
summary: "حذف دور",
security: adminSecurity,
requestBody: requestBody(
{
type: "object",
properties: {
id: { type: "string" },
},
required: ["id"],
},
{ id: "round_1" }
),
responses: Object.fromEntries([
["200", jsonResponse("دور حذف شد", { $ref: "#/components/schemas/SuccessResponse" })],
errorResponse("400", "این دور دارای بازی است", "این دور دارای بازی است و قابل حذف نیست"),
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/rounds/{id}/activate": {
post: {
tags: ["Rounds"],
summary: "فعال/غیرفعال کردن یک دور",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
responses: Object.fromEntries([
["200", jsonResponse("وضعیت دور تغییر کرد", { $ref: "#/components/schemas/Round" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
errorResponse("404", "دور پیدا نشد", "Round not found"),
]),
},
},
"/api/gameweeks": {
get: {
tags: ["Gameweeks"],
summary: "لیست هفته‌ها",
responses: {
"200": jsonResponse("لیست هفته‌ها", { type: "array", items: { $ref: "#/components/schemas/Gameweek" } }),
},
},
post: {
tags: ["Gameweeks"],
summary: "ایجاد هفته جدید",
security: adminSecurity,
requestBody: requestBody(
{
type: "object",
additionalProperties: true,
properties: {
number: { type: "integer" },
name: { type: "string" },
deadline: { type: "string", format: "date-time" },
},
},
{ number: 1, name: "Gameweek 1", deadline: "2026-06-10T12:00:00.000Z" }
),
responses: Object.fromEntries([
["201", jsonResponse("هفته ایجاد شد", { $ref: "#/components/schemas/Gameweek" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/gameweeks/{id}/activate": {
post: {
tags: ["Gameweeks"],
summary: "فعال کردن یک هفته",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
responses: Object.fromEntries([
["200", jsonResponse("هفته فعال شد", { $ref: "#/components/schemas/Gameweek" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/leaderboard": {
get: {
tags: ["Leaderboard"],
summary: "دریافت جدول رتبه‌بندی",
responses: {
"200": jsonResponse(
"لیست 50 تیم برتر",
{ type: "array", items: { $ref: "#/components/schemas/LeaderboardEntry" } }
),
},
},
},
"/api/fantasy-news": {
get: {
tags: ["Fantasy News"],
summary: "Get fantasy news",
parameters: [
{
in: "header",
name: "x-news-mode",
required: false,
schema: { type: "string", enum: ["latest"] },
description: "Use latest to return the latest 4 summarized items",
},
{
in: "header",
name: "x-news-summary",
required: false,
schema: { type: "string", enum: ["true"] },
description: "Use true to return the latest 4 summarized items",
},
],
responses: {
"200": jsonResponse("Fantasy news list", { $ref: "#/components/schemas/FantasyNewsListResponse" }),
},
},
post: {
tags: ["Fantasy News"],
summary: "Create fantasy news",
security: adminSecurity,
requestBody: requestBody(
{ $ref: "#/components/schemas/FantasyNewsCreateRequest" },
{
icon: "info",
title: "Lineup update",
description: "Confirmed team news before kickoff.",
newsTime: "2026-06-10T12:00:00.000Z",
}
),
responses: Object.fromEntries([
["201", jsonResponse("Fantasy news created", { $ref: "#/components/schemas/FantasyNews" })],
errorResponse("400", "Invalid fantasy news payload", "Invalid fantasy news payload"),
errorResponse("401", "Admin access required", "Unauthorized"),
]),
},
},
"/api/upload/player-image": {
post: {
tags: ["Upload"],
summary: "آپلود تصویر بازیکن",
requestBody: {
required: true,
content: {
"multipart/form-data": {
schema: {
type: "object",
properties: {
file: {
type: "string",
format: "binary",
},
},
required: ["file"],
},
},
},
},
responses: Object.fromEntries([
["200", jsonResponse("فایل آپلود شد", { $ref: "#/components/schemas/UploadPlayerImageResponse" })],
errorResponse("400", "فایل نامعتبر", "فایلی انتخاب نشده است"),
errorResponse("500", "خطای داخلی سرور", "خطا در آپلود فایل"),
]),
},
},
"/api/payment/request": {
post: {
tags: ["Payment"],
summary: "ساخت درخواست پرداخت زرین‌پال",
security: authSecurity,
requestBody: requestBody(
{ $ref: "#/components/schemas/PaymentRequestPayload" },
{ packageId: "pkg_123" }
),
responses: Object.fromEntries([
["200", jsonResponse("آدرس پرداخت ایجاد شد", { $ref: "#/components/schemas/PaymentRequestResponse" })],
errorResponse("400", "خطا در ساخت پرداخت", "خطا در اتصال به درگاه"),
errorResponse("401", "نیازمند ورود", "Unauthorized"),
errorResponse("404", "پکیج پیدا نشد", "پکیج پیدا نشد"),
]),
},
},
"/api/payment/verify": {
get: {
tags: ["Payment"],
summary: "تایید پرداخت و ریدایرکت به فروشگاه",
description: "این Endpoint توسط زرین‌پال فراخوانی می‌شود و در نهایت کاربر را به `/shop` با query string مناسب منتقل می‌کند.",
parameters: [
{ in: "query", name: "Authority", required: false, schema: { type: "string" } },
{ in: "query", name: "Status", required: false, schema: { type: "string", example: "OK" } },
],
responses: {
"307": {
description: "ریدایرکت به /shop با وضعیت success, failed, cancelled یا error",
},
},
},
},
"/api/quiz": {
get: {
tags: ["Quiz"],
summary: "دریافت کوئیز روز جاری",
responses: {
"200": jsonResponse(
"کوئیز روز یا null",
{
oneOf: [{ $ref: "#/components/schemas/DailyQuiz" }, { type: "null" }],
}
),
},
},
},
"/api/quiz/submit": {
post: {
tags: ["Quiz"],
summary: "ارسال پاسخ‌های کوئیز",
security: authSecurity,
requestBody: requestBody(
{ $ref: "#/components/schemas/QuizSubmitRequest" },
{ quizId: "quiz_123", answers: [1, 2, 0] }
),
responses: Object.fromEntries([
[
"200",
jsonResponse("نتیجه ثبت شد", {
type: "object",
properties: {
score: { type: "integer" },
correct: { type: "integer" },
total: { type: "integer" },
submission: { type: "object", additionalProperties: true },
},
}),
],
errorResponse("400", "خطای ارسال یا خارج از بازه", "قبلاً شرکت کرده‌اید"),
errorResponse("401", "نیازمند ورود", "Unauthorized"),
errorResponse("404", "کوئیز پیدا نشد", "Quiz not found"),
]),
},
},
"/api/quiz/my-results": {
get: {
tags: ["Quiz"],
summary: "دریافت نتایج کوئیزهای کاربر",
security: authSecurity,
responses: Object.fromEntries([
["200", jsonResponse("نتایج کاربر", { type: "array", items: { type: "object", additionalProperties: true } })],
errorResponse("401", "نیازمند ورود", "Unauthorized"),
]),
},
},
"/api/admin/quiz": {
get: {
tags: ["Admin", "Quiz"],
summary: "لیست تمام کوئیزها برای ادمین",
security: adminSecurity,
responses: Object.fromEntries([
["200", jsonResponse("لیست کوئیزها", { type: "array", items: { type: "object", additionalProperties: true } })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
post: {
tags: ["Admin", "Quiz"],
summary: "ایجاد کوئیز جدید",
security: adminSecurity,
requestBody: requestBody(
{ $ref: "#/components/schemas/AdminQuizCreateRequest" },
{
date: "2026-06-10",
windowStart: "2026-06-10T08:00:00.000Z",
windowEnd: "2026-06-10T20:00:00.000Z",
goldWinnersCount: 1,
silverWinnersCount: 2,
bronzeWinnersCount: 0,
questions: [
{
questionText: "برنده بازی اول چه تیمی است؟",
options: ["A", "B", "C", "D"],
correctAnswer: 1,
},
],
}
),
responses: Object.fromEntries([
["201", jsonResponse("کوئیز ایجاد شد", { type: "object", additionalProperties: true })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/admin/quiz/{id}/lottery": {
post: {
tags: ["Admin", "Quiz", "Golden Cards"],
summary: "اجرای قرعه‌کشی برندگان کوئیز",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
responses: Object.fromEntries([
["200", jsonResponse("خروجی قرعه‌کشی", { type: "object", additionalProperties: true })],
errorResponse("400", "خطای قرعه‌کشی", "قرعه‌کشی قبلاً انجام شده"),
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
errorResponse("404", "کوئیز پیدا نشد", "Quiz not found"),
]),
},
},
"/api/golden-cards": {
get: {
tags: ["Golden Cards"],
summary: "لیست کارت‌های طلایی کاربر جاری",
security: authSecurity,
responses: Object.fromEntries([
["200", jsonResponse("لیست کارت‌ها", { type: "array", items: { $ref: "#/components/schemas/GoldenCard" } })],
errorResponse("401", "نیازمند ورود", "Unauthorized"),
]),
},
},
"/api/golden-cards/{id}/reveal": {
post: {
tags: ["Golden Cards"],
summary: "باز کردن کارت طلایی",
security: authSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
responses: Object.fromEntries([
["200", jsonResponse("کارت باز شد", { $ref: "#/components/schemas/GoldenCard" })],
errorResponse("400", "کارت قبلاً باز شده", "کارت قبلاً باز شده"),
errorResponse("401", "نیازمند ورود", "Unauthorized"),
errorResponse("403", "کارت متعلق به کاربر نیست", "Forbidden"),
errorResponse("404", "کارت پیدا نشد", "Card not found"),
]),
},
},
"/api/admin/teams": {
get: {
tags: ["Admin", "Team"],
summary: "لیست تیم‌ها برای ادمین",
security: adminSecurity,
responses: Object.fromEntries([
["200", jsonResponse("لیست تیم‌ها", { type: "array", items: { type: "object", additionalProperties: true } })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/admin/teams/{id}": {
put: {
tags: ["Admin", "Team"],
summary: "تغییر وضعیت یک تیم",
security: adminSecurity,
parameters: [{ in: "path", name: "id", required: true, schema: { type: "string" } }],
requestBody: requestBody(
{ $ref: "#/components/schemas/AdminTeamStatusUpdateRequest" },
{ status: "APPROVED" }
),
responses: Object.fromEntries([
["200", jsonResponse("وضعیت تیم به‌روزرسانی شد", { $ref: "#/components/schemas/Team" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
"/api/admin/scoring": {
put: {
tags: ["Admin"],
summary: "به‌روزرسانی قوانین امتیازدهی",
security: adminSecurity,
requestBody: requestBody(
{
type: "array",
items: { $ref: "#/components/schemas/ScoringRuleInput" },
},
[{ position: "FWD", eventType: "GOAL", points: 4 }]
),
responses: Object.fromEntries([
["200", jsonResponse("قوانین ذخیره شدند", { $ref: "#/components/schemas/SuccessResponse" })],
errorResponse("401", "نیازمند دسترسی ادمین", "Unauthorized"),
]),
},
},
},
};