snapshot_poker_phase3
This commit is contained in:
@@ -268,6 +268,251 @@ async function changeCookies(
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* IDEMPOTENT COOKIE CHANGE
|
||||
* ============================================================
|
||||
*
|
||||
* Applies a Cookie mutation at most once for a user-scoped
|
||||
* operationId. This is useful for persistent game buy-ins and
|
||||
* cash-outs that may be retried after a crash or restart.
|
||||
*
|
||||
* The Cookie balance mutation and operation marker are written
|
||||
* atomically to the same user document.
|
||||
*/
|
||||
|
||||
async function changeCookiesOnce(
|
||||
userId,
|
||||
amount,
|
||||
operationId
|
||||
) {
|
||||
if (
|
||||
!Number.isSafeInteger(
|
||||
amount
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
"INVALID_COOKIE_AMOUNT"
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
typeof operationId !==
|
||||
"string"
|
||||
||
|
||||
!/^[A-Za-z0-9:_-]{1,160}$/.test(
|
||||
operationId
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
"INVALID_COOKIE_OPERATION_ID"
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
amount ===
|
||||
0
|
||||
) {
|
||||
return {
|
||||
applied:
|
||||
false,
|
||||
balance:
|
||||
await getCookieBalance(
|
||||
userId
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
cookies
|
||||
} =
|
||||
getCollections();
|
||||
|
||||
const filter = {
|
||||
userId,
|
||||
appliedCookieOperations: {
|
||||
$ne:
|
||||
operationId
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
amount <
|
||||
0
|
||||
) {
|
||||
filter.cookies = {
|
||||
$gte:
|
||||
Math.abs(
|
||||
amount
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
const update = {
|
||||
$inc: {
|
||||
cookies:
|
||||
amount
|
||||
},
|
||||
$addToSet: {
|
||||
appliedCookieOperations:
|
||||
operationId
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
amount >
|
||||
0
|
||||
) {
|
||||
update.$setOnInsert = {
|
||||
userId
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const result =
|
||||
await cookies.updateOne(
|
||||
filter,
|
||||
update,
|
||||
{
|
||||
upsert:
|
||||
amount > 0
|
||||
}
|
||||
);
|
||||
|
||||
if (
|
||||
result.modifiedCount ===
|
||||
1
|
||||
||
|
||||
result.upsertedCount ===
|
||||
1
|
||||
) {
|
||||
return {
|
||||
applied:
|
||||
true,
|
||||
balance:
|
||||
await getCookieBalance(
|
||||
userId
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
catch (error) {
|
||||
/*
|
||||
* A concurrent positive retry can race an upsert.
|
||||
* The unique userId index may reject the losing insert.
|
||||
* Re-read below and treat an already-recorded operation
|
||||
* as a successful idempotent retry.
|
||||
*/
|
||||
if (
|
||||
error?.code !==
|
||||
11000
|
||||
) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const document =
|
||||
await cookies.findOne(
|
||||
{
|
||||
userId
|
||||
},
|
||||
{
|
||||
projection: {
|
||||
cookies:
|
||||
1,
|
||||
appliedCookieOperations:
|
||||
1
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const operations =
|
||||
Array.isArray(
|
||||
document
|
||||
?.appliedCookieOperations
|
||||
)
|
||||
? document
|
||||
.appliedCookieOperations
|
||||
: [];
|
||||
|
||||
if (
|
||||
operations.includes(
|
||||
operationId
|
||||
)
|
||||
) {
|
||||
return {
|
||||
applied:
|
||||
false,
|
||||
balance:
|
||||
Math.max(
|
||||
0,
|
||||
Number(
|
||||
document?.cookies ??
|
||||
0
|
||||
)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
amount <
|
||||
0
|
||||
) {
|
||||
throw new Error(
|
||||
"INSUFFICIENT_COOKIES"
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"COOKIE_OPERATION_FAILED"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
async function hasCookieOperation(
|
||||
userId,
|
||||
operationId
|
||||
) {
|
||||
if (
|
||||
typeof operationId !==
|
||||
"string"
|
||||
||
|
||||
!/^[A-Za-z0-9:_-]{1,160}$/.test(
|
||||
operationId
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
"INVALID_COOKIE_OPERATION_ID"
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
cookies
|
||||
} =
|
||||
getCollections();
|
||||
|
||||
const document =
|
||||
await cookies.findOne(
|
||||
{
|
||||
userId,
|
||||
appliedCookieOperations:
|
||||
operationId
|
||||
},
|
||||
{
|
||||
projection: {
|
||||
_id:
|
||||
1
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return Boolean(
|
||||
document
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* COOKIE TRANSFER
|
||||
@@ -1188,6 +1433,8 @@ module.exports = {
|
||||
|
||||
getCookieBalance,
|
||||
changeCookies,
|
||||
changeCookiesOnce,
|
||||
hasCookieOperation,
|
||||
transferCookies,
|
||||
|
||||
getCooldown,
|
||||
|
||||
Reference in New Issue
Block a user