1444 lines
53 KiB
TypeScript
1444 lines
53 KiB
TypeScript
const authSecurity = [{ SessionToken: [] }, { SecureSessionToken: [] }];
|
||
const adminSecurity = [{ SessionToken: [] }, { SecureSessionToken: [] }];
|
||
|
||
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",
|
||
},
|
||
},
|
||
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" },
|
||
},
|
||
},
|
||
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" },
|
||
},
|
||
},
|
||
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/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/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"),
|
||
]),
|
||
},
|
||
},
|
||
},
|
||
};
|