ballrush-core 0.13.0 → 1.3.2
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/domain/event.d.ts +4 -0
- package/dist/domain/event.js +7 -2
- package/dist/mongo/schemas/event.schema.d.ts +1 -0
- package/dist/mongo/schemas/event.schema.js +1 -0
- package/dist/ports/events.repository.d.ts +1 -0
- package/dist/ports/groups.repository.d.ts +9 -0
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +1 -0
- package/dist/repositories/mongo/event.mapper.js +2 -0
- package/dist/repositories/mongo/group-repo.events.d.ts +42 -0
- package/dist/repositories/mongo/group-repo.events.js +19 -0
- package/dist/repositories/mongo/group.repository.d.ts +2 -1
- package/dist/repositories/mongo/group.repository.js +134 -0
- package/package.json +1 -1
package/dist/domain/event.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare class Event {
|
|
|
14
14
|
private gameMode;
|
|
15
15
|
private reportMessageId?;
|
|
16
16
|
private status;
|
|
17
|
+
private excludeFromStats;
|
|
17
18
|
private constructor();
|
|
18
19
|
static create(params: {
|
|
19
20
|
eventId: number;
|
|
@@ -30,6 +31,7 @@ export declare class Event {
|
|
|
30
31
|
isRatingApplied?: boolean;
|
|
31
32
|
status?: EventStatus;
|
|
32
33
|
gameMode?: GameMode;
|
|
34
|
+
excludeFromStats?: boolean;
|
|
33
35
|
}): Event;
|
|
34
36
|
getEventId(): number;
|
|
35
37
|
getGroupId(): number;
|
|
@@ -44,6 +46,7 @@ export declare class Event {
|
|
|
44
46
|
getCreatedBy(): number;
|
|
45
47
|
getStatus(): EventStatus;
|
|
46
48
|
getIsRatingApplied(): boolean;
|
|
49
|
+
getExcludeFromStats(): boolean;
|
|
47
50
|
getGameMode(): GameMode;
|
|
48
51
|
canChangeGameMode(): boolean;
|
|
49
52
|
setGameMode(mode: GameMode): void;
|
|
@@ -54,6 +57,7 @@ export declare class Event {
|
|
|
54
57
|
replaceTeams(teams: Team[]): void;
|
|
55
58
|
replaceMatches(matches: Match[]): void;
|
|
56
59
|
setRatingApplied(mark: boolean): void;
|
|
60
|
+
setExcludeFromStats(value: boolean): void;
|
|
57
61
|
setStatus(status: EventStatus): void;
|
|
58
62
|
isActive(): boolean;
|
|
59
63
|
isInProgress(): boolean;
|
package/dist/domain/event.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Event = void 0;
|
|
4
4
|
const types_1 = require("../types");
|
|
5
5
|
class Event {
|
|
6
|
-
constructor(eventId, groupId, messageId, templateId, date, participants, teams, matches, createdBy, isRatingApplied, seasonId, gameMode, reportMessageId, status = "active") {
|
|
6
|
+
constructor(eventId, groupId, messageId, templateId, date, participants, teams, matches, createdBy, isRatingApplied, seasonId, gameMode, reportMessageId, status = "active", excludeFromStats = false) {
|
|
7
7
|
this.eventId = eventId;
|
|
8
8
|
this.groupId = groupId;
|
|
9
9
|
this.messageId = messageId;
|
|
@@ -18,12 +18,13 @@ class Event {
|
|
|
18
18
|
this.gameMode = gameMode;
|
|
19
19
|
this.reportMessageId = reportMessageId;
|
|
20
20
|
this.status = status;
|
|
21
|
+
this.excludeFromStats = excludeFromStats;
|
|
21
22
|
}
|
|
22
23
|
static create(params) {
|
|
23
24
|
if (!params.seasonId) {
|
|
24
25
|
throw new Error("Event must have a season ID");
|
|
25
26
|
}
|
|
26
|
-
return new Event(params.eventId, params.groupId, params.messageId ?? 0, params.templateId, params.date, params.participants ?? [], params.teams ?? [], params.matches ?? [], params.createdBy, params.isRatingApplied ?? false, params.seasonId, params.gameMode ?? types_1.DEFAULT_GAME_MODE, params.reportMessageId, params.status ?? "active");
|
|
27
|
+
return new Event(params.eventId, params.groupId, params.messageId ?? 0, params.templateId, params.date, params.participants ?? [], params.teams ?? [], params.matches ?? [], params.createdBy, params.isRatingApplied ?? false, params.seasonId, params.gameMode ?? types_1.DEFAULT_GAME_MODE, params.reportMessageId, params.status ?? "active", params.excludeFromStats ?? false);
|
|
27
28
|
}
|
|
28
29
|
getEventId() { return this.eventId; }
|
|
29
30
|
getGroupId() { return this.groupId; }
|
|
@@ -38,6 +39,7 @@ class Event {
|
|
|
38
39
|
getCreatedBy() { return this.createdBy; }
|
|
39
40
|
getStatus() { return this.status; }
|
|
40
41
|
getIsRatingApplied() { return this.isRatingApplied; }
|
|
42
|
+
getExcludeFromStats() { return this.excludeFromStats; }
|
|
41
43
|
getGameMode() { return this.gameMode; }
|
|
42
44
|
canChangeGameMode() { return this.matches.length === 0; }
|
|
43
45
|
setGameMode(mode) {
|
|
@@ -67,6 +69,9 @@ class Event {
|
|
|
67
69
|
setRatingApplied(mark) {
|
|
68
70
|
this.isRatingApplied = mark;
|
|
69
71
|
}
|
|
72
|
+
setExcludeFromStats(value) {
|
|
73
|
+
this.excludeFromStats = value;
|
|
74
|
+
}
|
|
70
75
|
setStatus(status) {
|
|
71
76
|
this.status = status;
|
|
72
77
|
}
|
|
@@ -57,6 +57,7 @@ function createEventSchema() {
|
|
|
57
57
|
teams: { type: [TeamSchema], default: [] },
|
|
58
58
|
matches: { type: [MatchSchema], default: [] },
|
|
59
59
|
isRatingApplied: { type: Boolean },
|
|
60
|
+
excludeFromStats: { type: Boolean, default: false },
|
|
60
61
|
reportMessageId: { type: Number },
|
|
61
62
|
gameMode: {
|
|
62
63
|
type: String,
|
|
@@ -14,10 +14,19 @@ export type GroupWrite = {
|
|
|
14
14
|
language: LanguageType;
|
|
15
15
|
isPublic: boolean;
|
|
16
16
|
};
|
|
17
|
+
export type GroupFieldsUpdate = Partial<Pick<GroupWrite, "groupName" | "timezone" | "isMessageLast" | "language" | "isPublic">>;
|
|
17
18
|
export interface GroupsRepository {
|
|
18
19
|
create(groupData: Group): Promise<Group>;
|
|
19
20
|
findById(groupId: number): Promise<Group | null>;
|
|
20
21
|
update(groupId: number, updates: Group): Promise<Group | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Partial $set update for non-participant scalar fields.
|
|
24
|
+
* Use this instead of update() whenever you only need to change metadata
|
|
25
|
+
* (timezone, language, isMessageLast, etc.) — update() rewrites participants
|
|
26
|
+
* from the in-memory cache and can clobber rating data written by other
|
|
27
|
+
* processes between cache load and save.
|
|
28
|
+
*/
|
|
29
|
+
updateFields(groupId: number, fields: GroupFieldsUpdate): Promise<void>;
|
|
21
30
|
findAll(): Promise<Group[]>;
|
|
22
31
|
delete(groupId: number): Promise<void>;
|
|
23
32
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from "./mongo/user.repository";
|
|
2
2
|
export * from "./mongo/group.repository";
|
|
3
|
+
export * from "./mongo/group-repo.events";
|
|
3
4
|
export * from "./mongo/event.repository";
|
|
4
5
|
export * from "./mongo/event-template.repository";
|
|
5
6
|
export * from "./mongo/stat-group.repository";
|
|
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./mongo/user.repository"), exports);
|
|
18
18
|
__exportStar(require("./mongo/group.repository"), exports);
|
|
19
|
+
__exportStar(require("./mongo/group-repo.events"), exports);
|
|
19
20
|
__exportStar(require("./mongo/event.repository"), exports);
|
|
20
21
|
__exportStar(require("./mongo/event-template.repository"), exports);
|
|
21
22
|
__exportStar(require("./mongo/stat-group.repository"), exports);
|
|
@@ -20,6 +20,7 @@ function toDomain(doc) {
|
|
|
20
20
|
reportMessageId: doc.reportMessageId,
|
|
21
21
|
status: doc.status,
|
|
22
22
|
gameMode: doc.gameMode ?? types_1.DEFAULT_GAME_MODE,
|
|
23
|
+
excludeFromStats: doc.excludeFromStats ?? false,
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
26
|
function toDoc(event) {
|
|
@@ -38,5 +39,6 @@ function toDoc(event) {
|
|
|
38
39
|
isRatingApplied: event.getIsRatingApplied(),
|
|
39
40
|
reportMessageId: event.getReportMessageId(),
|
|
40
41
|
gameMode: event.getGameMode(),
|
|
42
|
+
excludeFromStats: event.getExcludeFromStats(),
|
|
41
43
|
};
|
|
42
44
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
export type SuspiciousGroupUpdateReason = "rating_regression" | "history_truncated";
|
|
3
|
+
export interface SuspiciousGroupUpdate {
|
|
4
|
+
groupId: number;
|
|
5
|
+
reason: SuspiciousGroupUpdateReason;
|
|
6
|
+
/** Sum of all rating points across all participants — before this update */
|
|
7
|
+
sumBefore: number;
|
|
8
|
+
/** Sum of all rating points across all participants — after this update would be applied */
|
|
9
|
+
sumAfter: number;
|
|
10
|
+
/** sumAfter - sumBefore (negative = regression) */
|
|
11
|
+
delta: number;
|
|
12
|
+
/** Total ratingHistory entries in the document — before */
|
|
13
|
+
historyCountBefore: number;
|
|
14
|
+
/** Total ratingHistory entries — after */
|
|
15
|
+
historyCountAfter: number;
|
|
16
|
+
/** Top 10 individual losses (userId, delta), sorted by largest loss first */
|
|
17
|
+
topLosses: Array<{
|
|
18
|
+
userId: number;
|
|
19
|
+
deltaSum: number;
|
|
20
|
+
deltaHistoryLen: number;
|
|
21
|
+
}>;
|
|
22
|
+
/** First N lines of the call stack pointing at the caller */
|
|
23
|
+
callerStack: string;
|
|
24
|
+
/** true if the repository refused to apply the write because of regression guard */
|
|
25
|
+
refused: boolean;
|
|
26
|
+
/** ISO timestamp of when the event fired */
|
|
27
|
+
timestamp: string;
|
|
28
|
+
}
|
|
29
|
+
declare class GroupRepoEvents extends EventEmitter {
|
|
30
|
+
emitSuspicious(ev: SuspiciousGroupUpdate): void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Singleton emitter the MongoGroupsRepository fires events on whenever it
|
|
34
|
+
* detects something unusual (e.g. a write that would drop total rating
|
|
35
|
+
* significantly, or shorten history). Wire a listener in the app's bootstrap
|
|
36
|
+
* to forward these to Telegram, Slack, etc.
|
|
37
|
+
*
|
|
38
|
+
* import { groupRepoEvents } from "ballrush-core/repositories";
|
|
39
|
+
* groupRepoEvents.on("suspicious", (ev) => { ... });
|
|
40
|
+
*/
|
|
41
|
+
export declare const groupRepoEvents: GroupRepoEvents;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.groupRepoEvents = void 0;
|
|
4
|
+
const events_1 = require("events");
|
|
5
|
+
class GroupRepoEvents extends events_1.EventEmitter {
|
|
6
|
+
emitSuspicious(ev) {
|
|
7
|
+
this.emit("suspicious", ev);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Singleton emitter the MongoGroupsRepository fires events on whenever it
|
|
12
|
+
* detects something unusual (e.g. a write that would drop total rating
|
|
13
|
+
* significantly, or shorten history). Wire a listener in the app's bootstrap
|
|
14
|
+
* to forward these to Telegram, Slack, etc.
|
|
15
|
+
*
|
|
16
|
+
* import { groupRepoEvents } from "ballrush-core/repositories";
|
|
17
|
+
* groupRepoEvents.on("suspicious", (ev) => { ... });
|
|
18
|
+
*/
|
|
19
|
+
exports.groupRepoEvents = new GroupRepoEvents();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Model } from "mongoose";
|
|
2
|
-
import type { GroupsRepository } from "../../ports/groups.repository";
|
|
2
|
+
import type { GroupsRepository, GroupFieldsUpdate } from "../../ports/groups.repository";
|
|
3
3
|
import type { GroupDoc } from "../../mongo";
|
|
4
4
|
import { Group } from "../../domain/group";
|
|
5
5
|
export declare class MongoGroupsRepository implements GroupsRepository {
|
|
@@ -8,6 +8,7 @@ export declare class MongoGroupsRepository implements GroupsRepository {
|
|
|
8
8
|
create(group: Group): Promise<Group>;
|
|
9
9
|
findById(groupId: number): Promise<Group | null>;
|
|
10
10
|
update(groupId: number, updates: Group): Promise<Group | null>;
|
|
11
|
+
updateFields(groupId: number, fields: GroupFieldsUpdate): Promise<void>;
|
|
11
12
|
findAll(): Promise<Group[]>;
|
|
12
13
|
delete(groupId: number): Promise<void>;
|
|
13
14
|
}
|
|
@@ -3,6 +3,71 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MongoGroupsRepository = void 0;
|
|
4
4
|
const group_mapper_1 = require("./group.mapper");
|
|
5
5
|
const errors_1 = require("../../errors");
|
|
6
|
+
const group_repo_events_1 = require("./group-repo.events");
|
|
7
|
+
// Regression guard thresholds. Tunable via env:
|
|
8
|
+
// GROUPS_REGRESSION_GUARD=off — disable refusal (still logs + emits event)
|
|
9
|
+
// GROUPS_REGRESSION_ABS_DROP — absolute sum drop that counts as suspicious (default 50)
|
|
10
|
+
// GROUPS_REGRESSION_PCT_DROP — fractional drop (0..1) that counts as suspicious (default 0.05)
|
|
11
|
+
function getRegressionGuardConfig() {
|
|
12
|
+
const enabled = (process.env.GROUPS_REGRESSION_GUARD ?? "on") !== "off";
|
|
13
|
+
const absDrop = Number(process.env.GROUPS_REGRESSION_ABS_DROP ?? 50);
|
|
14
|
+
const pctDrop = Number(process.env.GROUPS_REGRESSION_PCT_DROP ?? 0.05);
|
|
15
|
+
return { enabled, absDrop, pctDrop };
|
|
16
|
+
}
|
|
17
|
+
function sumOfHistory(participants) {
|
|
18
|
+
if (!participants)
|
|
19
|
+
return 0;
|
|
20
|
+
let total = 0;
|
|
21
|
+
for (const p of participants) {
|
|
22
|
+
for (const r of p.ratingHistory ?? [])
|
|
23
|
+
total += r.value ?? 0;
|
|
24
|
+
}
|
|
25
|
+
return total;
|
|
26
|
+
}
|
|
27
|
+
function countHistory(participants) {
|
|
28
|
+
if (!participants)
|
|
29
|
+
return 0;
|
|
30
|
+
let total = 0;
|
|
31
|
+
for (const p of participants)
|
|
32
|
+
total += (p.ratingHistory ?? []).length;
|
|
33
|
+
return total;
|
|
34
|
+
}
|
|
35
|
+
function computeTopLosses(before, after, limit = 10) {
|
|
36
|
+
const beforeBy = new Map();
|
|
37
|
+
for (const p of before)
|
|
38
|
+
beforeBy.set(p.userId, p);
|
|
39
|
+
const losses = [];
|
|
40
|
+
for (const a of after) {
|
|
41
|
+
const b = beforeBy.get(a.userId);
|
|
42
|
+
if (!b)
|
|
43
|
+
continue;
|
|
44
|
+
const bSum = (b.ratingHistory ?? []).reduce((s, r) => s + (r.value ?? 0), 0);
|
|
45
|
+
const aSum = (a.ratingHistory ?? []).reduce((s, r) => s + (r.value ?? 0), 0);
|
|
46
|
+
const deltaSum = aSum - bSum;
|
|
47
|
+
const deltaHistoryLen = (a.ratingHistory ?? []).length - (b.ratingHistory ?? []).length;
|
|
48
|
+
if (deltaSum < 0 || deltaHistoryLen < 0) {
|
|
49
|
+
losses.push({ userId: a.userId, deltaSum: Math.round(deltaSum * 100) / 100, deltaHistoryLen });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// also detect participants that disappeared entirely
|
|
53
|
+
const afterIds = new Set(after.map(p => p.userId));
|
|
54
|
+
for (const b of before) {
|
|
55
|
+
if (!afterIds.has(b.userId)) {
|
|
56
|
+
const bSum = (b.ratingHistory ?? []).reduce((s, r) => s + (r.value ?? 0), 0);
|
|
57
|
+
losses.push({
|
|
58
|
+
userId: b.userId,
|
|
59
|
+
deltaSum: Math.round(-bSum * 100) / 100,
|
|
60
|
+
deltaHistoryLen: -(b.ratingHistory ?? []).length,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
losses.sort((x, y) => x.deltaSum - y.deltaSum);
|
|
65
|
+
return losses.slice(0, limit);
|
|
66
|
+
}
|
|
67
|
+
function shortStack(depth = 8) {
|
|
68
|
+
const raw = new Error().stack ?? "";
|
|
69
|
+
return raw.split("\n").slice(2, 2 + depth).join("\n");
|
|
70
|
+
}
|
|
6
71
|
class MongoGroupsRepository {
|
|
7
72
|
constructor(GroupModel) {
|
|
8
73
|
this.GroupModel = GroupModel;
|
|
@@ -58,6 +123,59 @@ class MongoGroupsRepository {
|
|
|
58
123
|
throw new errors_1.ValidationError('Group', 'updates', updates, 'updates object is required');
|
|
59
124
|
}
|
|
60
125
|
const write = (0, group_mapper_1.toDoc)(updates);
|
|
126
|
+
// --- Observability: log every full-document write of a group ---
|
|
127
|
+
// We log enough context to find the culprit after the fact if the
|
|
128
|
+
// historical Pattaya-style regression ever happens again.
|
|
129
|
+
const callerStack = shortStack();
|
|
130
|
+
const sumAfter = sumOfHistory(write.participants);
|
|
131
|
+
const historyCountAfter = countHistory(write.participants);
|
|
132
|
+
// Read current state ONCE for regression guard + alert payload.
|
|
133
|
+
const currentDoc = await this.GroupModel.findOne({ groupId }).lean();
|
|
134
|
+
const sumBefore = sumOfHistory(currentDoc?.participants ?? []);
|
|
135
|
+
const historyCountBefore = countHistory(currentDoc?.participants ?? []);
|
|
136
|
+
const delta = sumAfter - sumBefore;
|
|
137
|
+
const historyDelta = historyCountAfter - historyCountBefore;
|
|
138
|
+
console.log(`[GROUPS_UPDATE] groupId=${groupId} participants=${write.participants.length} ` +
|
|
139
|
+
`sumBefore=${sumBefore.toFixed(2)} sumAfter=${sumAfter.toFixed(2)} delta=${delta.toFixed(2)} ` +
|
|
140
|
+
`histBefore=${historyCountBefore} histAfter=${historyCountAfter} histDelta=${historyDelta}\n` +
|
|
141
|
+
`[GROUPS_UPDATE_STACK]\n${callerStack}`);
|
|
142
|
+
// --- Regression guard ---
|
|
143
|
+
const guard = getRegressionGuardConfig();
|
|
144
|
+
const absThreshold = Math.max(guard.absDrop, Math.abs(sumBefore) * guard.pctDrop);
|
|
145
|
+
const isRegression = currentDoc !== null && (delta < -absThreshold || // big rating drop
|
|
146
|
+
historyDelta < 0 // history shrank
|
|
147
|
+
);
|
|
148
|
+
if (isRegression) {
|
|
149
|
+
const topLosses = computeTopLosses((currentDoc?.participants ?? []), write.participants);
|
|
150
|
+
const event = {
|
|
151
|
+
groupId,
|
|
152
|
+
reason: (delta < -absThreshold ? "rating_regression" : "history_truncated"),
|
|
153
|
+
sumBefore: Math.round(sumBefore * 100) / 100,
|
|
154
|
+
sumAfter: Math.round(sumAfter * 100) / 100,
|
|
155
|
+
delta: Math.round(delta * 100) / 100,
|
|
156
|
+
historyCountBefore,
|
|
157
|
+
historyCountAfter,
|
|
158
|
+
topLosses,
|
|
159
|
+
callerStack,
|
|
160
|
+
refused: guard.enabled,
|
|
161
|
+
timestamp: new Date().toISOString(),
|
|
162
|
+
};
|
|
163
|
+
console.error(`[GROUPS_REGRESSION] groupId=${groupId} reason=${event.reason} ` +
|
|
164
|
+
`delta=${event.delta} histDelta=${historyDelta} refused=${event.refused}\n` +
|
|
165
|
+
`topLosses=${JSON.stringify(event.topLosses)}\n` +
|
|
166
|
+
`[GROUPS_REGRESSION_STACK]\n${callerStack}`);
|
|
167
|
+
// Fire-and-forget event for any listener (e.g. Telegram alerter in the bot).
|
|
168
|
+
try {
|
|
169
|
+
group_repo_events_1.groupRepoEvents.emitSuspicious(event);
|
|
170
|
+
}
|
|
171
|
+
catch { /* never fail update because of a listener */ }
|
|
172
|
+
if (guard.enabled) {
|
|
173
|
+
// Refuse the write — return null so callers can detect failure.
|
|
174
|
+
// To bypass intentionally (e.g. legit reset by admin), run with:
|
|
175
|
+
// GROUPS_REGRESSION_GUARD=off
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
61
179
|
const doc = await this.GroupModel.findOneAndUpdate({ groupId }, { $set: write }, { new: true }).lean();
|
|
62
180
|
return doc ? (0, group_mapper_1.toDomain)(doc) : null;
|
|
63
181
|
}
|
|
@@ -68,6 +186,22 @@ class MongoGroupsRepository {
|
|
|
68
186
|
throw new errors_1.QueryError('update', 'Group', groupId, error);
|
|
69
187
|
}
|
|
70
188
|
}
|
|
189
|
+
async updateFields(groupId, fields) {
|
|
190
|
+
try {
|
|
191
|
+
if (!groupId || groupId === 0) {
|
|
192
|
+
throw new errors_1.ValidationError('Group', 'groupId', groupId, 'must be a non-zero number');
|
|
193
|
+
}
|
|
194
|
+
if (!fields || Object.keys(fields).length === 0)
|
|
195
|
+
return;
|
|
196
|
+
await this.GroupModel.updateOne({ groupId }, { $set: fields });
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
if (error instanceof errors_1.ValidationError) {
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
throw new errors_1.QueryError('updateFields', 'Group', groupId, error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
71
205
|
async findAll() {
|
|
72
206
|
try {
|
|
73
207
|
const docs = await this.GroupModel.find().lean();
|