|
| 1 | +import type { PipelineStage } from "mongoose"; |
| 2 | +import type { CockSeason } from "@/shared/lib/seasons"; |
| 3 | + |
| 4 | +// =========================================== |
| 5 | +// Результаты агрегаций |
| 6 | +// =========================================== |
| 7 | + |
| 8 | +interface SeasonRange { |
| 9 | + start: Date; |
| 10 | + end: Date; |
| 11 | + season_num: number; |
| 12 | +} |
| 13 | + |
| 14 | +export interface AggSeasonPosition { |
| 15 | + season_num: number; |
| 16 | + position: number; |
| 17 | +} |
| 18 | + |
| 19 | +// =========================================== |
| 20 | +// Пайплайны |
| 21 | +// =========================================== |
| 22 | + |
| 23 | +/** |
| 24 | + * Позиция пользователя в каждом завершённом сезоне (1-based). |
| 25 | + * Возвращает массив { season_num, position } только для сезонов, где юзер участвовал. |
| 26 | + */ |
| 27 | +export const pUserPositionsInSeasons = (userId: number, completedSeasons: SeasonRange[]): PipelineStage[] => { |
| 28 | + if (completedSeasons.length === 0) return [{ $limit: 0 }]; |
| 29 | + |
| 30 | + const first = completedSeasons[0]; |
| 31 | + const last = completedSeasons[completedSeasons.length - 1]; |
| 32 | + if (!first || !last) return [{ $limit: 0 }]; |
| 33 | + |
| 34 | + const thenKey = "then"; |
| 35 | + const branches = completedSeasons.map((s) => ({ |
| 36 | + case: { $and: [{ $gte: ["$requested_at", s.start] }, { $lt: ["$requested_at", s.end] }] }, |
| 37 | + [thenKey]: s.season_num, |
| 38 | + })); |
| 39 | + |
| 40 | + return [ |
| 41 | + { $match: { requested_at: { $gte: first.start, $lt: last.end } } }, |
| 42 | + { $addFields: { season_num: { $switch: { branches, default: null } } } }, |
| 43 | + { $match: { season_num: { $ne: null } } }, |
| 44 | + { $group: { _id: { season_num: "$season_num", user_id: "$user_id" }, total_size: { $sum: "$size" } } }, |
| 45 | + { $sort: { "_id.season_num": 1, total_size: -1 } }, |
| 46 | + { $group: { _id: "$_id.season_num", users: { $push: { user_id: "$_id.user_id", total_size: "$total_size" } } } }, |
| 47 | + { |
| 48 | + $project: { |
| 49 | + _id: 0, |
| 50 | + season_num: "$_id", |
| 51 | + position: { |
| 52 | + $add: [{ $indexOfArray: ["$users.user_id", userId] }, 1], |
| 53 | + }, |
| 54 | + }, |
| 55 | + }, |
| 56 | + // position=0 означает юзер не найден (indexOfArray вернул -1, +1 = 0) |
| 57 | + { $match: { position: { $gt: 0 } } }, |
| 58 | + ]; |
| 59 | +}; |
| 60 | + |
| 61 | +export const toSeasonRanges = (seasons: CockSeason[]): SeasonRange[] => |
| 62 | + seasons |
| 63 | + .filter((s) => !s.is_active) |
| 64 | + .map((s) => ({ |
| 65 | + start: new Date(s.start_date), |
| 66 | + end: new Date(s.end_date), |
| 67 | + season_num: s.season_num, |
| 68 | + })); |
| 69 | + |
| 70 | +/** Дата первого кока в коллекции. */ |
| 71 | +export const pFirstCockDate = (): PipelineStage[] => [{ $sort: { requested_at: 1 } }, { $limit: 1 }, { $project: { _id: 0, first_date: "$requested_at" } }]; |
0 commit comments