@arken/node 1.5.1 → 1.5.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/db.ts +76 -1
- package/index.ts +351 -18
- package/package.json +3 -3
- package/tsconfig.json +33 -2
- package/util.ts +1 -0
- package/modules/area/area.models.ts +0 -15
- package/modules/area/area.router.ts +0 -74
- package/modules/area/area.schema.ts +0 -22
- package/modules/area/area.service.ts +0 -124
- package/modules/area/area.types.ts +0 -26
- package/modules/area/index.ts +0 -5
- package/modules/asset/asset.models.ts +0 -59
- package/modules/asset/asset.router.ts +0 -55
- package/modules/asset/asset.schema.ts +0 -27
- package/modules/asset/asset.service.ts +0 -85
- package/modules/asset/asset.types.ts +0 -22
- package/modules/asset/index.ts +0 -5
- package/modules/chain/chain.models.ts +0 -50
- package/modules/chain/chain.router.ts +0 -104
- package/modules/chain/chain.schema.ts +0 -52
- package/modules/chain/chain.service.ts +0 -167
- package/modules/chain/chain.types.ts +0 -24
- package/modules/chain/index.ts +0 -5
- package/modules/character/character.models.ts +0 -174
- package/modules/character/character.router.ts +0 -314
- package/modules/character/character.schema.ts +0 -147
- package/modules/character/character.service.ts +0 -876
- package/modules/character/character.types.ts +0 -64
- package/modules/character/index.ts +0 -5
- package/modules/chat/chat.models.ts +0 -43
- package/modules/chat/chat.router.ts +0 -67
- package/modules/chat/chat.schema.ts +0 -36
- package/modules/chat/chat.service.ts +0 -128
- package/modules/chat/chat.types.ts +0 -20
- package/modules/chat/index.ts +0 -5
- package/modules/collection/collection.models.ts +0 -76
- package/modules/collection/collection.router.ts +0 -91
- package/modules/collection/collection.schema.ts +0 -90
- package/modules/collection/collection.service.ts +0 -192
- package/modules/collection/collection.types.ts +0 -36
- package/modules/collection/index.ts +0 -5
- package/modules/core/core.models.ts +0 -1380
- package/modules/core/core.router.ts +0 -1781
- package/modules/core/core.schema.ts +0 -847
- package/modules/core/core.service.ts +0 -2824
- package/modules/core/core.types.ts +0 -340
- package/modules/core/index.ts +0 -5
- package/modules/core/mail/applyPatchesOrMail.ts +0 -568
- package/modules/core/mail/mailClaimablePatchesBatch.ts +0 -381
- package/modules/game/game.models.ts +0 -53
- package/modules/game/game.router.ts +0 -110
- package/modules/game/game.schema.ts +0 -23
- package/modules/game/game.service.ts +0 -143
- package/modules/game/game.types.ts +0 -28
- package/modules/game/index.ts +0 -5
- package/modules/interface/index.ts +0 -5
- package/modules/interface/interface.canonicalize.ts +0 -279
- package/modules/interface/interface.models.ts +0 -40
- package/modules/interface/interface.router.ts +0 -175
- package/modules/interface/interface.schema.ts +0 -59
- package/modules/interface/interface.service.ts +0 -356
- package/modules/interface/interface.types.ts +0 -25
- package/modules/item/index.ts +0 -5
- package/modules/item/item.models.ts +0 -124
- package/modules/item/item.router.ts +0 -103
- package/modules/item/item.schema.ts +0 -120
- package/modules/item/item.service.ts +0 -167
- package/modules/item/item.types.ts +0 -74
- package/modules/job/index.ts +0 -5
- package/modules/job/job.models.ts +0 -14
- package/modules/job/job.router.ts +0 -44
- package/modules/job/job.schema.ts +0 -9
- package/modules/job/job.service.ts +0 -243
- package/modules/job/job.types.ts +0 -23
- package/modules/market/index.ts +0 -5
- package/modules/market/market.models.ts +0 -113
- package/modules/market/market.router.ts +0 -73
- package/modules/market/market.schema.ts +0 -140
- package/modules/market/market.service.ts +0 -122
- package/modules/market/market.types.ts +0 -56
- package/modules/product/index.ts +0 -5
- package/modules/product/product.models.ts +0 -166
- package/modules/product/product.router.ts +0 -93
- package/modules/product/product.schema.ts +0 -149
- package/modules/product/product.service.ts +0 -160
- package/modules/product/product.types.ts +0 -33
- package/modules/profile/index.ts +0 -5
- package/modules/profile/profile.models.ts +0 -214
- package/modules/profile/profile.router.ts +0 -72
- package/modules/profile/profile.schema.ts +0 -156
- package/modules/profile/profile.service.ts +0 -149
- package/modules/profile/profile.types.ts +0 -22
- package/modules/raffle/index.ts +0 -5
- package/modules/raffle/raffle.models.ts +0 -44
- package/modules/raffle/raffle.router.ts +0 -90
- package/modules/raffle/raffle.schema.ts +0 -32
- package/modules/raffle/raffle.service.ts +0 -167
- package/modules/raffle/raffle.types.ts +0 -30
- package/modules/skill/index.ts +0 -5
- package/modules/skill/skill.models.ts +0 -16
- package/modules/skill/skill.router.ts +0 -201
- package/modules/skill/skill.schema.ts +0 -40
- package/modules/skill/skill.service.ts +0 -390
- package/modules/skill/skill.types.ts +0 -33
- package/modules/video/index.ts +0 -5
- package/modules/video/video.models.ts +0 -25
- package/modules/video/video.router.ts +0 -143
- package/modules/video/video.schema.ts +0 -46
- package/modules/video/video.service.ts +0 -274
- package/modules/video/video.types.ts +0 -33
- package/util/db/index.ts +0 -7
- package/util/db/isPostgresError.ts +0 -9
- package/util/db/isUniqueConstraintViolation.ts +0 -3
- package/util/db.ts +0 -62
- package/util/index.ts +0 -351
- /package/{util/api.ts → api.ts} +0 -0
- /package/{util/array.ts → array.ts} +0 -0
- /package/{util/browser.ts → browser.ts} +0 -0
- /package/{util/codebase.ts → codebase.ts} +0 -0
- /package/{util/config.ts → config.ts} +0 -0
- /package/{util/decoder.test.ts → decoder.test.ts} +0 -0
- /package/{util/decoder.ts → decoder.ts} +0 -0
- /package/{util/format.ts → format.ts} +0 -0
- /package/{util/guid.ts → guid.ts} +0 -0
- /package/{util/json.ts → json.ts} +0 -0
- /package/{util/log.ts → log.ts} +0 -0
- /package/{util/math.ts → math.ts} +0 -0
- /package/{util/merkle.ts → merkle.ts} +0 -0
- /package/{util/mongo.ts → mongo.ts} +0 -0
- /package/{util/number.ts → number.ts} +0 -0
- /package/{util/object.ts → object.ts} +0 -0
- /package/{util/otp.ts → otp.ts} +0 -0
- /package/{util/physics.ts → physics.ts} +0 -0
- /package/{util/process.ts → process.ts} +0 -0
- /package/{util/rpc.ts → rpc.ts} +0 -0
- /package/{util/seer.ts → seer.ts} +0 -0
- /package/{util/string.ts → string.ts} +0 -0
- /package/{util/text.ts → text.ts} +0 -0
- /package/{util/time → time}/date.ts +0 -0
- /package/{util/time → time}/fancyTimeFormat.ts +0 -0
- /package/{util/time → time}/index.ts +0 -0
- /package/{util/time → time}/now.ts +0 -0
- /package/{util/types → types}/mongo.d.ts +0 -0
- /package/{util/web3 → web3}/httpProvider.ts +0 -0
- /package/{util/web3.ts → web3.ts} +0 -0
- /package/{util/websocket.ts → websocket.ts} +0 -0
- /package/{util/zk.ts → zk.ts} +0 -0
- /package/{util/zod.ts → zod.ts} +0 -0
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
// packages/node/modules/core/mail/mailClaimablePatchesBatch.ts
|
|
2
|
-
//
|
|
3
|
-
import type { RouterContext } from '../core.types';
|
|
4
|
-
import type { EntityPatch } from '../../../types';
|
|
5
|
-
|
|
6
|
-
type MailKind = 'mail' | 'dm' | 'group' | 'support' | 'system';
|
|
7
|
-
|
|
8
|
-
export type MailPatchMessagePayload = {
|
|
9
|
-
kind: 'patch-grant';
|
|
10
|
-
source: string;
|
|
11
|
-
title?: string;
|
|
12
|
-
body?: string;
|
|
13
|
-
patches: EntityPatch[];
|
|
14
|
-
ui?: {
|
|
15
|
-
rewards?: Array<{ type: 'item' | 'token' | 'reward'; id: string; quantity?: number; meta?: any }>;
|
|
16
|
-
effects?: Array<{ type: 'stat' | 'flag' | 'effect'; key?: string; delta?: number; value?: any; label?: string }>;
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export type MailClaimableBatchParams = {
|
|
21
|
-
ctx: RouterContext;
|
|
22
|
-
|
|
23
|
-
/** If omitted, helper streams all profiles */
|
|
24
|
-
profileIds?: string[];
|
|
25
|
-
|
|
26
|
-
/** Streaming mode: process all profiles using _id cursor */
|
|
27
|
-
streamAllProfiles?: boolean;
|
|
28
|
-
|
|
29
|
-
kind?: MailKind; // default 'mail'
|
|
30
|
-
source: string;
|
|
31
|
-
|
|
32
|
-
title: string;
|
|
33
|
-
body: string;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Same dedupeKey across all conversations is OK because uniqueness is enforced by:
|
|
37
|
-
* (conversationId, claim.dedupeKey)
|
|
38
|
-
*/
|
|
39
|
-
dedupeKey: string;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* IMPORTANT: These patches should be claimable. Your applyPatchesWithInventoryViaMail
|
|
43
|
-
* uses `patch.claimable` to decide mail vs immediate. For broadcast we ALWAYS mail.
|
|
44
|
-
*/
|
|
45
|
-
claimablePatches: EntityPatch[];
|
|
46
|
-
|
|
47
|
-
payloadUi?: MailPatchMessagePayload['ui'];
|
|
48
|
-
|
|
49
|
-
batchSize?: number; // default 1000
|
|
50
|
-
|
|
51
|
-
conversationKey: string; // ✅ REQUIRED
|
|
52
|
-
conversationTitle?: string; // optional: used on new convo creation
|
|
53
|
-
conversationCategory?: string; // optional
|
|
54
|
-
conversationImportance?: number; // optional
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export type MailClaimableBatchResult = {
|
|
58
|
-
processedProfiles: number;
|
|
59
|
-
conversationsFound: number;
|
|
60
|
-
conversationsCreated: number;
|
|
61
|
-
|
|
62
|
-
messagesExisting: number;
|
|
63
|
-
messagesCreated: number;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
function chunk<T>(arr: T[], size: number): T[][] {
|
|
67
|
-
const out: T[][] = [];
|
|
68
|
-
for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size));
|
|
69
|
-
return out;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function toObjectId(mongoose: any, id: string) {
|
|
73
|
-
// Works whether id already is ObjectId-like or string
|
|
74
|
-
try {
|
|
75
|
-
return new mongoose.Types.ObjectId(id);
|
|
76
|
-
} catch {
|
|
77
|
-
return id;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Bulk-mail claimable patches to many profiles.
|
|
83
|
-
*
|
|
84
|
-
* Design assumptions:
|
|
85
|
-
* - 1 mailbox per profile per kind (kind='mail' for inbox)
|
|
86
|
-
* - Conversation can be found by:
|
|
87
|
-
* - kind + profileId (back-compat) OR
|
|
88
|
-
* - kind + participants.profileId (new)
|
|
89
|
-
* - Dedupe per conversation by claim.dedupeKey
|
|
90
|
-
*/
|
|
91
|
-
export async function mailClaimablePatchesBatch(params: MailClaimableBatchParams): Promise<MailClaimableBatchResult> {
|
|
92
|
-
const {
|
|
93
|
-
ctx,
|
|
94
|
-
kind = 'mail',
|
|
95
|
-
source,
|
|
96
|
-
title,
|
|
97
|
-
body,
|
|
98
|
-
dedupeKey,
|
|
99
|
-
claimablePatches,
|
|
100
|
-
payloadUi,
|
|
101
|
-
batchSize = 1000,
|
|
102
|
-
conversationKey,
|
|
103
|
-
} = params;
|
|
104
|
-
|
|
105
|
-
const mongoose = (ctx.app as any).db?.mongoose ?? (ctx.app as any).mongoose;
|
|
106
|
-
const Profile = (ctx.app as any).model.Profile;
|
|
107
|
-
const Conversation = (ctx.app as any).model.Conversation;
|
|
108
|
-
const ConversationMessage = (ctx.app as any).model.ConversationMessage;
|
|
109
|
-
|
|
110
|
-
if (!Profile || !Conversation || !ConversationMessage) {
|
|
111
|
-
throw new Error('mailClaimablePatchesBatch: missing required models (Profile/Conversation/ConversationMessage)');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const payload: MailPatchMessagePayload = {
|
|
115
|
-
kind: 'patch-grant',
|
|
116
|
-
source,
|
|
117
|
-
title,
|
|
118
|
-
body,
|
|
119
|
-
patches: claimablePatches,
|
|
120
|
-
ui: payloadUi,
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const preview = `${title} — ${body}`.slice(0, 140);
|
|
124
|
-
const now = new Date();
|
|
125
|
-
|
|
126
|
-
let processedProfiles = 0;
|
|
127
|
-
let conversationsFound = 0;
|
|
128
|
-
let conversationsCreated = 0;
|
|
129
|
-
let messagesExisting = 0;
|
|
130
|
-
let messagesCreated = 0;
|
|
131
|
-
|
|
132
|
-
// ----------------------------
|
|
133
|
-
// Resolve profile id batches
|
|
134
|
-
// ----------------------------
|
|
135
|
-
const explicitIds = Array.isArray(params.profileIds) ? params.profileIds.filter(Boolean) : [];
|
|
136
|
-
|
|
137
|
-
// If caller gave explicit profileIds, just run those (chunked).
|
|
138
|
-
if (explicitIds.length > 0) {
|
|
139
|
-
for (const batch of chunk(explicitIds, batchSize)) {
|
|
140
|
-
const r = await processProfileIdBatch(batch, ctx);
|
|
141
|
-
processedProfiles += r.processedProfiles;
|
|
142
|
-
conversationsFound += r.conversationsFound;
|
|
143
|
-
conversationsCreated += r.conversationsCreated;
|
|
144
|
-
messagesExisting += r.messagesExisting;
|
|
145
|
-
messagesCreated += r.messagesCreated;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return { processedProfiles, conversationsFound, conversationsCreated, messagesExisting, messagesCreated };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Otherwise stream all profiles if requested; default true if neither profileIds nor streamAllProfiles specified.
|
|
152
|
-
const streamAll = params.streamAllProfiles ?? true;
|
|
153
|
-
if (!streamAll) {
|
|
154
|
-
return {
|
|
155
|
-
processedProfiles: 0,
|
|
156
|
-
conversationsFound: 0,
|
|
157
|
-
conversationsCreated: 0,
|
|
158
|
-
messagesExisting: 0,
|
|
159
|
-
messagesCreated: 0,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Stream profiles by _id cursor to avoid loading everything.
|
|
164
|
-
let lastId: any = null;
|
|
165
|
-
|
|
166
|
-
// eslint-disable-next-line no-constant-condition
|
|
167
|
-
while (true) {
|
|
168
|
-
const q: any = lastId ? { _id: { $gt: lastId } } : {};
|
|
169
|
-
const docs = await Profile.find(q).select({ _id: 1 }).sort({ _id: 1 }).limit(batchSize).lean().exec();
|
|
170
|
-
|
|
171
|
-
if (!docs || docs.length === 0) break;
|
|
172
|
-
|
|
173
|
-
const batchIds = docs.map((d: any) => String(d._id));
|
|
174
|
-
lastId = docs[docs.length - 1]._id;
|
|
175
|
-
|
|
176
|
-
const r = await processProfileIdBatch(batchIds, ctx);
|
|
177
|
-
processedProfiles += r.processedProfiles;
|
|
178
|
-
conversationsFound += r.conversationsFound;
|
|
179
|
-
conversationsCreated += r.conversationsCreated;
|
|
180
|
-
messagesExisting += r.messagesExisting;
|
|
181
|
-
messagesCreated += r.messagesCreated;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return { processedProfiles, conversationsFound, conversationsCreated, messagesExisting, messagesCreated };
|
|
185
|
-
|
|
186
|
-
// ============================================================
|
|
187
|
-
// Batch worker: takes string profile ids
|
|
188
|
-
// ============================================================
|
|
189
|
-
async function processProfileIdBatch(profileIds: string[], ctx: any): Promise<MailClaimableBatchResult> {
|
|
190
|
-
const profileObjectIds = profileIds.map((id) => toObjectId(mongoose, id));
|
|
191
|
-
|
|
192
|
-
// 1) Find existing inbox conversations for these profiles (both back-compat + participants)
|
|
193
|
-
const existingConvos = await ctx.app.model.Conversation.find({
|
|
194
|
-
kind,
|
|
195
|
-
key: conversationKey, // ✅ NEW
|
|
196
|
-
$or: [{ profileId: { $in: profileObjectIds } }, { 'participants.profileId': { $in: profileObjectIds } }],
|
|
197
|
-
})
|
|
198
|
-
.select({ _id: 1, profileId: 1, participants: 1 })
|
|
199
|
-
.lean()
|
|
200
|
-
.exec();
|
|
201
|
-
|
|
202
|
-
const convoByProfileId = new Map<string, any>();
|
|
203
|
-
|
|
204
|
-
for (const c of existingConvos || []) {
|
|
205
|
-
// Prefer participant mapping when available
|
|
206
|
-
const participants = Array.isArray((c as any).participants) ? (c as any).participants : [];
|
|
207
|
-
for (const p of participants) {
|
|
208
|
-
if (p?.profileId) convoByProfileId.set(String(p.profileId), c);
|
|
209
|
-
}
|
|
210
|
-
if ((c as any).profileId) convoByProfileId.set(String((c as any).profileId), c);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const foundCount = convoByProfileId.size;
|
|
214
|
-
|
|
215
|
-
// 2) Create missing conversations
|
|
216
|
-
const missingProfileIds = profileIds.filter((pid) => !convoByProfileId.has(String(pid)));
|
|
217
|
-
|
|
218
|
-
// 2) Create missing conversations (UPSERT)
|
|
219
|
-
if (missingProfileIds.length > 0) {
|
|
220
|
-
const ops = missingProfileIds.map((pid) => {
|
|
221
|
-
const pidObj = toObjectId(mongoose, pid);
|
|
222
|
-
return {
|
|
223
|
-
updateOne: {
|
|
224
|
-
filter: {
|
|
225
|
-
applicationId: toObjectId(mongoose, (ctx.app as any).filters.applicationId),
|
|
226
|
-
kind,
|
|
227
|
-
key: conversationKey,
|
|
228
|
-
profileId: pidObj,
|
|
229
|
-
status: { $ne: 'Archived' },
|
|
230
|
-
},
|
|
231
|
-
update: {
|
|
232
|
-
$setOnInsert: {
|
|
233
|
-
applicationId: toObjectId(mongoose, (ctx.app as any).filters.applicationId),
|
|
234
|
-
kind,
|
|
235
|
-
key: conversationKey,
|
|
236
|
-
profileId: pidObj,
|
|
237
|
-
participants: [
|
|
238
|
-
{
|
|
239
|
-
profileId: pidObj,
|
|
240
|
-
role: 'user',
|
|
241
|
-
lastReadAt: new Date(0),
|
|
242
|
-
unreadCount: 0,
|
|
243
|
-
isMuted: false,
|
|
244
|
-
isPinned: false,
|
|
245
|
-
isArchived: false,
|
|
246
|
-
isDeleted: false,
|
|
247
|
-
},
|
|
248
|
-
],
|
|
249
|
-
isLocked: true,
|
|
250
|
-
allowUserSend: false,
|
|
251
|
-
name: 'System',
|
|
252
|
-
category: 'system',
|
|
253
|
-
importance: 0,
|
|
254
|
-
lastMessageDate: null,
|
|
255
|
-
lastMessagePreview: '',
|
|
256
|
-
messageCount: 0,
|
|
257
|
-
messages: [],
|
|
258
|
-
status: 'Active',
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
upsert: true,
|
|
262
|
-
},
|
|
263
|
-
};
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
// bulkWrite exists on your wrapper (you already call it later)
|
|
267
|
-
const res = await ctx.app.model.Conversation.bulkWrite(ops, { ordered: false });
|
|
268
|
-
|
|
269
|
-
// 🔴 REQUIRED: re-read conversations to get _id for newly upserted docs
|
|
270
|
-
const convosForBatch = await ctx.app.model.Conversation.find({
|
|
271
|
-
kind,
|
|
272
|
-
key: conversationKey,
|
|
273
|
-
$or: [{ profileId: { $in: profileObjectIds } }, { 'participants.profileId': { $in: profileObjectIds } }],
|
|
274
|
-
})
|
|
275
|
-
.select({ _id: 1, profileId: 1, participants: 1 })
|
|
276
|
-
.lean()
|
|
277
|
-
.exec();
|
|
278
|
-
|
|
279
|
-
// rebuild convoByProfileId with fresh _id values
|
|
280
|
-
convoByProfileId.clear();
|
|
281
|
-
for (const c of convosForBatch || []) {
|
|
282
|
-
const participants = Array.isArray(c.participants) ? c.participants : [];
|
|
283
|
-
for (const p of participants) {
|
|
284
|
-
if (p?.profileId) convoByProfileId.set(String(p.profileId), c);
|
|
285
|
-
}
|
|
286
|
-
if (c.profileId) convoByProfileId.set(String(c.profileId), c);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Depending on wrapper, it may return { upsertedCount } or { nUpserted }
|
|
290
|
-
conversationsCreated += Number((res as any)?.upsertedCount ?? (res as any)?.nUpserted ?? 0);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// 3) Now we have conversation ids for (almost) everyone in the batch
|
|
294
|
-
const convoIds = profileIds.map((pid) => convoByProfileId.get(String(pid))?._id).filter(Boolean);
|
|
295
|
-
|
|
296
|
-
// If something is still missing, skip those profiles (should be extremely rare)
|
|
297
|
-
const convoIdSet = new Set(convoIds.map((id: any) => String(id)));
|
|
298
|
-
const uniqueConvoIds = Array.from(convoIdSet).map((id) => toObjectId(mongoose, id));
|
|
299
|
-
|
|
300
|
-
// 4) Dedupe: find existing messages with this dedupeKey for these conversations
|
|
301
|
-
const existingMsgs = await ctx.app.model.ConversationMessage.find({
|
|
302
|
-
conversationId: { $in: uniqueConvoIds },
|
|
303
|
-
'claim.dedupeKey': dedupeKey,
|
|
304
|
-
})
|
|
305
|
-
.select({ _id: 1, conversationId: 1 })
|
|
306
|
-
.lean()
|
|
307
|
-
.exec();
|
|
308
|
-
|
|
309
|
-
const hasMsgForConvo = new Set<string>((existingMsgs || []).map((m: any) => String(m.conversationId)));
|
|
310
|
-
const existingMsgCount = hasMsgForConvo.size;
|
|
311
|
-
|
|
312
|
-
// 5) Build inserts for conversations missing the message
|
|
313
|
-
const msgsToInsert = uniqueConvoIds
|
|
314
|
-
.filter((cid: any) => !hasMsgForConvo.has(String(cid)))
|
|
315
|
-
.map((conversationId: any) => ({
|
|
316
|
-
conversationId,
|
|
317
|
-
role: 'system',
|
|
318
|
-
type: 'reward',
|
|
319
|
-
content: body ?? '',
|
|
320
|
-
payload,
|
|
321
|
-
claim: {
|
|
322
|
-
isClaimable: true,
|
|
323
|
-
claimedDate: null,
|
|
324
|
-
claimedByProfileId: null,
|
|
325
|
-
dedupeKey,
|
|
326
|
-
attachments: [],
|
|
327
|
-
revokedDate: null,
|
|
328
|
-
revokeReason: null,
|
|
329
|
-
},
|
|
330
|
-
}));
|
|
331
|
-
|
|
332
|
-
let insertedMsgs: any[] = [];
|
|
333
|
-
if (msgsToInsert.length > 0) {
|
|
334
|
-
try {
|
|
335
|
-
insertedMsgs = await ctx.app.model.ConversationMessage.insertMany(msgsToInsert, { ordered: false });
|
|
336
|
-
} catch (e: any) {
|
|
337
|
-
console.log('error inserting conversation messages', e);
|
|
338
|
-
// If some duplicate insert races happened, we ignore and continue.
|
|
339
|
-
// console.warn('ConversationMessage.insertMany partial failure', e?.message);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// 6) Update conversations listing fields for those where we inserted a message
|
|
344
|
-
if (insertedMsgs.length > 0) {
|
|
345
|
-
const convoUpdates = insertedMsgs.map((m: any) => ({
|
|
346
|
-
updateOne: {
|
|
347
|
-
filter: { _id: m.conversationId },
|
|
348
|
-
update: {
|
|
349
|
-
$set: {
|
|
350
|
-
lastMessageDate: now,
|
|
351
|
-
lastMessagePreview: preview,
|
|
352
|
-
},
|
|
353
|
-
$inc: {
|
|
354
|
-
messageCount: 1,
|
|
355
|
-
// Increment unread count for the user participant if present
|
|
356
|
-
'participants.$[p].unreadCount': 1,
|
|
357
|
-
},
|
|
358
|
-
// Back-compat array; you said you’re keeping it for now.
|
|
359
|
-
$push: { messages: m._id },
|
|
360
|
-
},
|
|
361
|
-
arrayFilters: [{ 'p.role': 'user' }],
|
|
362
|
-
},
|
|
363
|
-
}));
|
|
364
|
-
|
|
365
|
-
try {
|
|
366
|
-
await ctx.app.model.Conversation.bulkWrite(convoUpdates, { ordered: false });
|
|
367
|
-
} catch (e: any) {
|
|
368
|
-
console.log('error bulk updating conversations', e);
|
|
369
|
-
// ignore
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return {
|
|
374
|
-
processedProfiles: profileIds.length,
|
|
375
|
-
conversationsFound: foundCount,
|
|
376
|
-
conversationsCreated: 0, // tracked at outer scope; keep per-batch minimal
|
|
377
|
-
messagesExisting: existingMsgCount,
|
|
378
|
-
messagesCreated: insertedMsgs.length,
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import * as mongo from '../../util/mongo';
|
|
2
|
-
import type * as Types from './game.types';
|
|
3
|
-
|
|
4
|
-
const { addTagVirtuals, addApplicationVirtual } = mongo;
|
|
5
|
-
|
|
6
|
-
export const Game = mongo.createModel<Types.GameDocument>(
|
|
7
|
-
'Game',
|
|
8
|
-
{
|
|
9
|
-
productId: { type: mongo.Schema.Types.ObjectId, ref: 'Product', required: true },
|
|
10
|
-
statId: { type: mongo.Schema.Types.ObjectId, ref: 'GameStat' },
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
extend: 'EntityFields',
|
|
14
|
-
indexes: [{ applicationId: 1, name: 1, unique: true }],
|
|
15
|
-
virtuals: [
|
|
16
|
-
...addTagVirtuals('Application'),
|
|
17
|
-
{ name: 'stat' },
|
|
18
|
-
{ name: 'stats', ref: 'GameStat', localField: '_id', foreignField: 'gameId' },
|
|
19
|
-
{ name: 'rounds', ref: 'GameRound', localField: '_id', foreignField: 'gameId' },
|
|
20
|
-
// {
|
|
21
|
-
// name: 'latestStat',
|
|
22
|
-
// ref: 'GameStat',
|
|
23
|
-
// localField: '_id',
|
|
24
|
-
// foreignField: 'gameId',
|
|
25
|
-
// options: { sort: { createdAt: -1 }, limit: 1 },
|
|
26
|
-
// },
|
|
27
|
-
],
|
|
28
|
-
}
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
export const GameStat = mongo.createModel<Types.GameStatDocument>(
|
|
32
|
-
'GameStat',
|
|
33
|
-
{
|
|
34
|
-
gameId: { type: mongo.Schema.Types.ObjectId, ref: 'Game', required: true },
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
extend: 'EntityFields',
|
|
38
|
-
virtuals: [...addTagVirtuals('Application'), { name: 'game' }],
|
|
39
|
-
}
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
export const GameRound = mongo.createModel<Types.GameRoundDocument>(
|
|
43
|
-
'GameRound',
|
|
44
|
-
{
|
|
45
|
-
gameId: { type: mongo.Schema.Types.ObjectId, ref: 'Game', required: true },
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
extend: 'EntityFields',
|
|
49
|
-
virtuals: [...addTagVirtuals('Application'), { name: 'game' }],
|
|
50
|
-
}
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
export const Era = mongo.createModel<Types.EraDocument>('Era', {});
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { z as zod } from 'zod';
|
|
2
|
-
import { initTRPC } from '@trpc/server';
|
|
3
|
-
import { customErrorFormatter, hasRole } from '../../util/rpc';
|
|
4
|
-
import type { RouterContext } from '../../types';
|
|
5
|
-
import { Era, Game, GameStat } from './game.schema';
|
|
6
|
-
import { Query, getQueryInput, inferRouterOutputs, inferRouterInputs } from '../../schema';
|
|
7
|
-
|
|
8
|
-
export const z = zod;
|
|
9
|
-
export const t = initTRPC.context<RouterContext>().create();
|
|
10
|
-
export const router = t.router;
|
|
11
|
-
export const procedure = t.procedure;
|
|
12
|
-
|
|
13
|
-
export const createRouter = () =>
|
|
14
|
-
router({
|
|
15
|
-
getGame: procedure
|
|
16
|
-
.use(hasRole('guest', t))
|
|
17
|
-
.use(customErrorFormatter(t))
|
|
18
|
-
.input(getQueryInput(Game))
|
|
19
|
-
.output(Game)
|
|
20
|
-
.query(({ input, ctx }) => (ctx.app.service.Game.getGame as any)(input, ctx)),
|
|
21
|
-
|
|
22
|
-
getGames: procedure
|
|
23
|
-
.use(hasRole('guest', t))
|
|
24
|
-
.use(customErrorFormatter(t))
|
|
25
|
-
.input(getQueryInput(Game))
|
|
26
|
-
.output(z.array(Game))
|
|
27
|
-
.query(({ input, ctx }) => (ctx.app.service.Game.getGames as any)(input, ctx)),
|
|
28
|
-
|
|
29
|
-
createGame: procedure
|
|
30
|
-
.use(hasRole('guest', t))
|
|
31
|
-
.use(customErrorFormatter(t))
|
|
32
|
-
.input(getQueryInput(Game))
|
|
33
|
-
.output(Game.pick({ id: true }))
|
|
34
|
-
.query(({ input, ctx }) => (ctx.app.service.Game.createGame as any)(input, ctx)),
|
|
35
|
-
|
|
36
|
-
updateGame: procedure
|
|
37
|
-
.use(hasRole('guest', t))
|
|
38
|
-
.use(customErrorFormatter(t))
|
|
39
|
-
.input(getQueryInput(Game))
|
|
40
|
-
.output(Game.pick({ id: true }))
|
|
41
|
-
.query(({ input, ctx }) => (ctx.app.service.Game.updateGame as any)(input, ctx)),
|
|
42
|
-
|
|
43
|
-
getGameStat: procedure
|
|
44
|
-
.use(hasRole('guest', t))
|
|
45
|
-
.use(customErrorFormatter(t))
|
|
46
|
-
.input(getQueryInput(GameStat))
|
|
47
|
-
.output(GameStat)
|
|
48
|
-
.query(({ input, ctx }) => (ctx.app.service.Game.getGameStat as any)(input, ctx)),
|
|
49
|
-
|
|
50
|
-
getGameStats: procedure
|
|
51
|
-
.use(hasRole('guest', t))
|
|
52
|
-
.use(customErrorFormatter(t))
|
|
53
|
-
.input(getQueryInput(GameStat))
|
|
54
|
-
.output(z.array(GameStat))
|
|
55
|
-
.query(({ input, ctx }) => (ctx.app.service.Game.getGameStats as any)(input, ctx)),
|
|
56
|
-
|
|
57
|
-
createGameStat: procedure
|
|
58
|
-
.use(hasRole('guest', t))
|
|
59
|
-
.use(customErrorFormatter(t))
|
|
60
|
-
.input(getQueryInput(GameStat))
|
|
61
|
-
.output(GameStat.pick({ id: true }))
|
|
62
|
-
.query(({ input, ctx }) => (ctx.app.service.Game.createGameStat as any)(input, ctx)),
|
|
63
|
-
|
|
64
|
-
updateGameStat: procedure
|
|
65
|
-
.use(hasRole('guest', t))
|
|
66
|
-
.use(customErrorFormatter(t))
|
|
67
|
-
.input(getQueryInput(GameStat))
|
|
68
|
-
.output(GameStat.pick({ id: true }))
|
|
69
|
-
.query(({ input, ctx }) => (ctx.app.service.Game.updateGame as any)(input, ctx)),
|
|
70
|
-
|
|
71
|
-
// Era Procedures
|
|
72
|
-
getEra: procedure
|
|
73
|
-
.use(hasRole('guest', t))
|
|
74
|
-
.use(customErrorFormatter(t))
|
|
75
|
-
.input(getQueryInput(Era))
|
|
76
|
-
.output(Era)
|
|
77
|
-
.query(({ input, ctx }) => (ctx.app.service.Game.getEra as any)(input, ctx)),
|
|
78
|
-
|
|
79
|
-
getEras: procedure
|
|
80
|
-
.use(hasRole('guest', t))
|
|
81
|
-
.use(customErrorFormatter(t))
|
|
82
|
-
.input(getQueryInput(Era))
|
|
83
|
-
.output(z.array(Era))
|
|
84
|
-
.query(({ input, ctx }) => (ctx.app.service.Game.getEras as any)(input, ctx)),
|
|
85
|
-
|
|
86
|
-
createEra: procedure
|
|
87
|
-
.use(hasRole('admin', t))
|
|
88
|
-
.use(customErrorFormatter(t))
|
|
89
|
-
.input(getQueryInput(Era))
|
|
90
|
-
.output(Era.pick({ id: true }))
|
|
91
|
-
.mutation(({ input, ctx }) => (ctx.app.service.Game.createEra as any)(input, ctx)),
|
|
92
|
-
|
|
93
|
-
updateEra: procedure
|
|
94
|
-
.use(hasRole('admin', t))
|
|
95
|
-
.use(customErrorFormatter(t))
|
|
96
|
-
.input(getQueryInput(Era))
|
|
97
|
-
.output(Era.pick({ id: true }))
|
|
98
|
-
.mutation(({ input, ctx }) => (ctx.app.service.Game.updateEra as any)(input, ctx)),
|
|
99
|
-
|
|
100
|
-
deleteEra: procedure
|
|
101
|
-
.use(hasRole('admin', t))
|
|
102
|
-
.use(customErrorFormatter(t))
|
|
103
|
-
.input(getQueryInput(Era))
|
|
104
|
-
.output(Era.pick({ id: true }))
|
|
105
|
-
.mutation(({ input, ctx }) => (ctx.app.service.Game.deleteEra as any)(input, ctx)),
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
export type Router = ReturnType<typeof createRouter>;
|
|
109
|
-
export type RouterInput = inferRouterInputs<Router>;
|
|
110
|
-
export type RouterOutput = inferRouterOutputs<Router>;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { z, ObjectId, Entity } from '../../schema';
|
|
2
|
-
|
|
3
|
-
export const GameStat = Entity.merge(
|
|
4
|
-
z.object({
|
|
5
|
-
gameId: ObjectId,
|
|
6
|
-
})
|
|
7
|
-
);
|
|
8
|
-
|
|
9
|
-
export const Game = Entity.merge(
|
|
10
|
-
z.object({
|
|
11
|
-
productId: ObjectId,
|
|
12
|
-
statId: ObjectId.optional(),
|
|
13
|
-
stat: GameStat.nullable().optional(),
|
|
14
|
-
})
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
export const GameRound = Entity.merge(
|
|
18
|
-
z.object({
|
|
19
|
-
gameId: ObjectId,
|
|
20
|
-
})
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
export const Era = Entity.merge(z.object({}));
|