ballrush-core 0.6.7 → 0.7.4
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.
- package/dist/mongo/schemas/stat-user-profile.schema.d.ts +3 -2
- package/dist/mongo/schemas/stat-user-profile.schema.js +3 -2
- package/dist/ports/index.d.ts +1 -0
- package/dist/ports/index.js +1 -0
- package/dist/ports/stat-user-context.repository.d.ts +16 -0
- package/dist/ports/stat-user-context.repository.js +2 -0
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +1 -0
- package/dist/repositories/mongo/stat-user-context.repository.d.ts +15 -0
- package/dist/repositories/mongo/stat-user-context.repository.js +119 -0
- package/dist/repositories/mongo/stat-user-profile.repository.js +2 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/stat-user-group-seasons.types.d.ts +9 -0
- package/dist/types/stat-user-group-seasons.types.js +6 -0
- package/dist/types/stat-user-groups.types.d.ts +11 -0
- package/dist/types/stat-user-groups.types.js +6 -0
- package/dist/types/stat-user-profile.types.d.ts +3 -2
- package/package.json +2 -2
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Schema } from "mongoose";
|
|
2
|
+
import { RatingSource } from "../../types";
|
|
2
3
|
/**
|
|
3
4
|
* Mongoose sub-schema for SkillHistoryPoint
|
|
4
5
|
*/
|
|
5
6
|
export interface SkillHistoryPointDoc {
|
|
6
7
|
date: Date;
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
value: number;
|
|
9
|
+
source: RatingSource;
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
12
|
* Mongoose sub-schema for ProfileAchievement
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createStatUserProfileSchema = createStatUserProfileSchema;
|
|
4
4
|
const mongoose_1 = require("mongoose");
|
|
5
|
+
const types_1 = require("../../types");
|
|
5
6
|
/**
|
|
6
7
|
* Factory function to create StatUserProfile schema
|
|
7
8
|
* Collection: stat_user_profile
|
|
@@ -9,8 +10,8 @@ const mongoose_1 = require("mongoose");
|
|
|
9
10
|
function createStatUserProfileSchema() {
|
|
10
11
|
const SkillHistoryPointSchema = new mongoose_1.Schema({
|
|
11
12
|
date: { type: Date, required: true },
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
value: { type: Number, required: true },
|
|
14
|
+
source: { type: String, enum: types_1.RatingSource, default: types_1.RatingSource.SYSTEM },
|
|
14
15
|
}, { _id: false });
|
|
15
16
|
const ProfileAchievementSchema = new mongoose_1.Schema({
|
|
16
17
|
icon: { type: String, required: true },
|
package/dist/ports/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/ports/index.js
CHANGED
|
@@ -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
|
+
}
|
|
@@ -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;
|
|
@@ -43,8 +43,8 @@ class MongoStatUserProfileRepository {
|
|
|
43
43
|
eventsCount: doc.eventsCount,
|
|
44
44
|
skillHistory: doc.skillHistory.map(point => ({
|
|
45
45
|
date: point.date,
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
value: point.value,
|
|
47
|
+
source: point.source,
|
|
48
48
|
})),
|
|
49
49
|
achievements: doc.achievements.map(achievement => ({
|
|
50
50
|
icon: achievement.icon,
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/types/index.js
CHANGED
|
@@ -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);
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* Statistics: User Profile Types
|
|
3
3
|
* Pre-aggregated user profile data for ballrush-api
|
|
4
4
|
*/
|
|
5
|
+
import { RatingSource } from "./shared";
|
|
5
6
|
export interface SkillHistoryPoint {
|
|
6
7
|
date: Date;
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
value: number;
|
|
9
|
+
source: RatingSource;
|
|
9
10
|
}
|
|
10
11
|
export interface ProfileAchievement {
|
|
11
12
|
icon: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ballrush-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"rimraf": "^5.0.0",
|
|
35
35
|
"ts-jest": "^29.4.4",
|
|
36
36
|
"ts-node": "^10.9.2",
|
|
37
|
-
"typescript": "^5.9.
|
|
37
|
+
"typescript": "^5.9.3"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|