ballrush-core 0.6.7 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,6 +9,7 @@ export * from "./stat-group-last-100-games.repository";
9
9
  export * from "./stat-group-activity-chart.repository";
10
10
  export * from "./stat-user-profile.repository";
11
11
  export * from "./stat-user-match-history.repository";
12
+ export * from "./stat-user-context.repository";
12
13
  export * from "./stat-event.repository";
13
14
  export * from "./seasons.repository";
14
15
  export * from "./achievements.repository";
@@ -25,6 +25,7 @@ __exportStar(require("./stat-group-last-100-games.repository"), exports);
25
25
  __exportStar(require("./stat-group-activity-chart.repository"), exports);
26
26
  __exportStar(require("./stat-user-profile.repository"), exports);
27
27
  __exportStar(require("./stat-user-match-history.repository"), exports);
28
+ __exportStar(require("./stat-user-context.repository"), exports);
28
29
  __exportStar(require("./stat-event.repository"), exports);
29
30
  __exportStar(require("./seasons.repository"), exports);
30
31
  __exportStar(require("./achievements.repository"), exports);
@@ -0,0 +1,16 @@
1
+ import { StatUserGroupItem } from "../types/stat-user-groups.types";
2
+ import { StatUserGroupSeasons } from "../types/stat-user-group-seasons.types";
3
+ /**
4
+ * Repository interface for user context data
5
+ * Provides methods to query user groups and seasons
6
+ */
7
+ export interface StatUserContextRepository {
8
+ /**
9
+ * Список групп, в которых пользователь имеет матчи / статистику.
10
+ */
11
+ findUserGroups(userId: number): Promise<StatUserGroupItem[]>;
12
+ /**
13
+ * Список уникальных сезонов пользователя для конкретной группы.
14
+ */
15
+ findUserSeasonsInGroup(userId: number, groupId: number): Promise<StatUserGroupSeasons | null>;
16
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -9,6 +9,7 @@ export * from "./mongo/stat-group-last-100-games.repository";
9
9
  export * from "./mongo/stat-group-activity-chart.repository";
10
10
  export * from "./mongo/stat-user-profile.repository";
11
11
  export * from "./mongo/stat-user-match-history.repository";
12
+ export * from "./mongo/stat-user-context.repository";
12
13
  export * from "./mongo/stat-event.repository";
13
14
  export * from "./mongo/season.repository";
14
15
  export * from "./mongo/achievement.repository";
@@ -25,6 +25,7 @@ __exportStar(require("./mongo/stat-group-last-100-games.repository"), exports);
25
25
  __exportStar(require("./mongo/stat-group-activity-chart.repository"), exports);
26
26
  __exportStar(require("./mongo/stat-user-profile.repository"), exports);
27
27
  __exportStar(require("./mongo/stat-user-match-history.repository"), exports);
28
+ __exportStar(require("./mongo/stat-user-context.repository"), exports);
28
29
  __exportStar(require("./mongo/stat-event.repository"), exports);
29
30
  __exportStar(require("./mongo/season.repository"), exports);
30
31
  __exportStar(require("./mongo/achievement.repository"), exports);
@@ -0,0 +1,15 @@
1
+ import type { Model } from "mongoose";
2
+ import type { StatUserContextRepository } from "../../ports/stat-user-context.repository";
3
+ import type { StatUserMatchHistoryDoc } from "../../mongo/schemas/stat-user-match-history.schema";
4
+ import type { StatUserGroupItem } from "../../types/stat-user-groups.types";
5
+ import type { StatUserGroupSeasons } from "../../types/stat-user-group-seasons.types";
6
+ /**
7
+ * MongoDB implementation of StatUserContextRepository
8
+ * Provides methods to query user groups and seasons from match history
9
+ */
10
+ export declare class MongoStatUserContextRepository implements StatUserContextRepository {
11
+ private readonly model;
12
+ constructor(model: Model<StatUserMatchHistoryDoc>);
13
+ findUserGroups(userId: number): Promise<StatUserGroupItem[]>;
14
+ findUserSeasonsInGroup(userId: number, groupId: number): Promise<StatUserGroupSeasons | null>;
15
+ }
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MongoStatUserContextRepository = void 0;
4
+ const errors_1 = require("../../errors");
5
+ /**
6
+ * MongoDB implementation of StatUserContextRepository
7
+ * Provides methods to query user groups and seasons from match history
8
+ */
9
+ class MongoStatUserContextRepository {
10
+ constructor(model) {
11
+ this.model = model;
12
+ }
13
+ async findUserGroups(userId) {
14
+ try {
15
+ // Validate input
16
+ if (!userId || userId === 0) {
17
+ throw new errors_1.ValidationError('StatUserContext', 'userId', userId, 'must be a non-zero number');
18
+ }
19
+ // Aggregation pipeline to get distinct groups with statistics
20
+ const pipeline = [
21
+ // Match all matches for the user
22
+ {
23
+ $match: {
24
+ userId: userId,
25
+ },
26
+ },
27
+ // Group by groupId to aggregate statistics
28
+ {
29
+ $group: {
30
+ _id: '$groupId',
31
+ totalMatches: { $sum: 1 },
32
+ lastMatchAt: { $max: '$date' },
33
+ },
34
+ },
35
+ // Lookup group name from Groups collection
36
+ {
37
+ $lookup: {
38
+ from: 'groups',
39
+ localField: '_id',
40
+ foreignField: 'groupId',
41
+ as: 'groupInfo',
42
+ },
43
+ },
44
+ // Unwind group info (handle case where group doesn't exist)
45
+ {
46
+ $unwind: {
47
+ path: '$groupInfo',
48
+ preserveNullAndEmptyArrays: true,
49
+ },
50
+ },
51
+ // Project final result
52
+ {
53
+ $project: {
54
+ userId: { $literal: userId },
55
+ groupId: '$_id',
56
+ groupName: {
57
+ $ifNull: ['$groupInfo.groupName', ''],
58
+ },
59
+ lastMatchAt: '$lastMatchAt',
60
+ totalMatches: '$totalMatches',
61
+ },
62
+ },
63
+ // Sort by lastMatchAt descending (most recent first)
64
+ {
65
+ $sort: { lastMatchAt: -1 },
66
+ },
67
+ ];
68
+ const results = await this.model.aggregate(pipeline).exec();
69
+ // Map to domain types
70
+ return results.map((item) => ({
71
+ userId: item.userId,
72
+ groupId: item.groupId,
73
+ groupName: item.groupName,
74
+ lastMatchAt: item.lastMatchAt ? new Date(item.lastMatchAt) : null,
75
+ totalMatches: item.totalMatches,
76
+ }));
77
+ }
78
+ catch (error) {
79
+ if (error instanceof errors_1.ValidationError) {
80
+ throw error;
81
+ }
82
+ throw new errors_1.QueryError('findUserGroups', 'StatUserContext', userId, error);
83
+ }
84
+ }
85
+ async findUserSeasonsInGroup(userId, groupId) {
86
+ try {
87
+ // Validate input
88
+ if (!userId || userId === 0) {
89
+ throw new errors_1.ValidationError('StatUserContext', 'userId', userId, 'must be a non-zero number');
90
+ }
91
+ if (!groupId || groupId === 0) {
92
+ throw new errors_1.ValidationError('StatUserContext', 'groupId', groupId, 'must be a non-zero number');
93
+ }
94
+ // Get distinct seasonIds for the user in the group
95
+ const seasonIds = await this.model.distinct('seasonId', {
96
+ userId,
97
+ groupId,
98
+ });
99
+ // If no seasons found, return null
100
+ if (!seasonIds || seasonIds.length === 0) {
101
+ return null;
102
+ }
103
+ // Sort seasons (assuming they're strings like "Autumn 2025")
104
+ const sortedSeasons = seasonIds.sort();
105
+ return {
106
+ userId,
107
+ groupId,
108
+ seasons: sortedSeasons,
109
+ };
110
+ }
111
+ catch (error) {
112
+ if (error instanceof errors_1.ValidationError) {
113
+ throw error;
114
+ }
115
+ throw new errors_1.QueryError('findUserSeasonsInGroup', 'StatUserContext', `${userId}:${groupId}`, error);
116
+ }
117
+ }
118
+ }
119
+ exports.MongoStatUserContextRepository = MongoStatUserContextRepository;
@@ -5,4 +5,6 @@ export * from "./stat-group-last-100-games.types";
5
5
  export * from "./stat-group-activity-chart.types";
6
6
  export * from "./stat-user-profile.types";
7
7
  export * from "./stat-user-match-history.types";
8
+ export * from "./stat-user-groups.types";
9
+ export * from "./stat-user-group-seasons.types";
8
10
  export * from "./stat-event.types";
@@ -21,4 +21,6 @@ __exportStar(require("./stat-group-last-100-games.types"), exports);
21
21
  __exportStar(require("./stat-group-activity-chart.types"), exports);
22
22
  __exportStar(require("./stat-user-profile.types"), exports);
23
23
  __exportStar(require("./stat-user-match-history.types"), exports);
24
+ __exportStar(require("./stat-user-groups.types"), exports);
25
+ __exportStar(require("./stat-user-group-seasons.types"), exports);
24
26
  __exportStar(require("./stat-event.types"), exports);
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Statistics: User Group Seasons Types
3
+ * Types for user seasons within a group
4
+ */
5
+ export interface StatUserGroupSeasons {
6
+ userId: number;
7
+ groupId: number;
8
+ seasons: string[];
9
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /**
3
+ * Statistics: User Group Seasons Types
4
+ * Types for user seasons within a group
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Statistics: User Groups Types
3
+ * Types for user group context data
4
+ */
5
+ export interface StatUserGroupItem {
6
+ userId: number;
7
+ groupId: number;
8
+ groupName: string;
9
+ lastMatchAt: Date | null;
10
+ totalMatches: number;
11
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /**
3
+ * Statistics: User Groups Types
4
+ * Types for user group context data
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ballrush-core",
3
- "version": "0.6.7",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",