mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 17:36:08 +08:00
core: fix problem solution vote race condition (#861)
This commit is contained in:
1 parent
598972b1c9
commit
8903486c6c
6 files changed
+54
-51
No files matched your search
@@ -5,8 +5,8 @@
|
||||
"main": "react.yaml",
|
||||
"dependencies": {
|
||||
"@stylistic/eslint-plugin": "^2.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.1",
|
||||
"@typescript-eslint/parser": "^8.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.1.0",
|
||||
"@typescript-eslint/parser": "^8.1.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-plugin-github": "^5.0.1",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
|
||||
@@ -86,7 +86,7 @@ class DataReportHandler extends Handler {
|
||||
this.ctx.emit('center/report', this, installId, old, payload);
|
||||
// TODO deliver messages
|
||||
this.response.body = { code: 0 };
|
||||
if (old.notification) this.response.body.notification = old?.notification;
|
||||
if (old?.notification) this.response.body.notification = old?.notification;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.627.0",
|
||||
"@aws-sdk/lib-storage": "3.627.0",
|
||||
"@aws-sdk/s3-presigned-post": "3.627.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.627.0",
|
||||
"@graphql-tools/schema": "^10.0.4",
|
||||
"@aws-sdk/client-s3": "3.631.0",
|
||||
"@aws-sdk/lib-storage": "3.631.0",
|
||||
"@aws-sdk/s3-presigned-post": "3.631.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.631.0",
|
||||
"@graphql-tools/schema": "^10.0.5",
|
||||
"@hydrooj/framework": "workspace:^",
|
||||
"@hydrooj/utils": "workspace:^",
|
||||
"@simplewebauthn/server": "9.0.3",
|
||||
|
||||
@@ -302,12 +302,18 @@ export function getMultiStatusWithoutDomain<K extends keyof DocStatusType>(
|
||||
}
|
||||
|
||||
export async function setStatus<K extends keyof DocStatusType>(
|
||||
domainId: string, docType: K, docId: DocStatusType[K]['docId'], uid: number, args: UpdateFilter<DocStatusType[K]>['$set'],
|
||||
domainId: string, docType: K, docId: DocStatusType[K]['docId'], uid: number,
|
||||
args: UpdateFilter<DocStatusType[K]>['$set'], returnDocument: 'before' | 'after' = 'after',
|
||||
): Promise<DocStatusType[K]> {
|
||||
const res = await collStatus.findOneAndUpdate(
|
||||
{ domainId, docType, docId, uid },
|
||||
{ $set: args },
|
||||
{ upsert: true, returnDocument: 'after' },
|
||||
{ $set: args, $inc: { counter: 1 } },
|
||||
{
|
||||
upsert: true,
|
||||
returnDocument,
|
||||
// if fetching document before update we want to ensure data was read from primary
|
||||
...(returnDocument === 'before' ? { readConcern: { level: 'majority' }, readPreference: 'primary' } : {}),
|
||||
},
|
||||
);
|
||||
return res.value;
|
||||
}
|
||||
@@ -321,41 +327,32 @@ export async function setMultiStatus<K extends keyof DocStatusType>(
|
||||
);
|
||||
}
|
||||
|
||||
export async function setStatusIfNotCondition<T extends keyof DocStatusType>(
|
||||
domainId: string, docType: T, docId: DocStatusType[T]['docId'], uid: number,
|
||||
filter: Filter<DocStatusType[T]>, args: Partial<DocStatusType[T]> = {},
|
||||
returnDocument: 'before' | 'after' = 'after',
|
||||
): Promise<DocStatusType[T]> {
|
||||
try {
|
||||
const res = await collStatus.findOneAndUpdate(
|
||||
{ domainId, docType, docId, uid, $nor: [filter] },
|
||||
{ $set: args },
|
||||
{ upsert: true, returnDocument },
|
||||
);
|
||||
return res.value;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function setIfNotStatus<T extends keyof DocStatusType, K extends keyof DocStatusType[T]>(
|
||||
domainId: string, docType: T, docId: DocStatusType[T]['docId'], uid: number,
|
||||
key: K, value: DocStatusType[T][K], ifNot: DocStatusType[T][K], args: Partial<DocStatusType[T]>,
|
||||
returnDocument: 'before' | 'after' = 'after',
|
||||
): Promise<DocStatusType[T]> {
|
||||
const current: Partial<DocStatusType[T]> = await collStatus.findOne({ domainId, docType, docId, uid }) || {};
|
||||
if (typeof key === 'string' && key.includes('.')) {
|
||||
const acc = key.split('.');
|
||||
let c = current;
|
||||
for (const i of acc) {
|
||||
if (!(i in c)) break;
|
||||
c = c[i];
|
||||
if (c === null || c === undefined) break;
|
||||
}
|
||||
if (c === ifNot) return null;
|
||||
} else if (current[key] === ifNot) return null;
|
||||
const res = await collStatus.findOneAndUpdate(
|
||||
{ domainId, docType, docId, uid },
|
||||
{ $set: { [key]: value, ...args } },
|
||||
{ upsert: true, returnDocument: 'after' },
|
||||
return await setStatusIfNotCondition(
|
||||
domainId, docType, docId, uid, { [key]: ifNot } as any,
|
||||
{ [key]: value, ...args }, returnDocument,
|
||||
);
|
||||
return res.value;
|
||||
}
|
||||
|
||||
export async function setStatusIfNotCondition<T extends keyof DocStatusType>(
|
||||
domainId: string, docType: T, docId: DocStatusType[T]['docId'], uid: number,
|
||||
filter: Filter<DocStatusType[T]>, args: Partial<DocStatusType[T]>,
|
||||
): Promise<DocStatusType[T]> {
|
||||
const match = await collStatus.findOne({ domainId, docType, docId, uid, ...filter });
|
||||
if (match) return null;
|
||||
const res = await collStatus.findOneAndUpdate(
|
||||
{ domainId, docType, docId, uid },
|
||||
{ $set: args },
|
||||
{ upsert: true, returnDocument: 'after' },
|
||||
);
|
||||
return res.value;
|
||||
}
|
||||
|
||||
export async function cappedIncStatus<T extends keyof DocStatusType>(
|
||||
|
||||
@@ -70,12 +70,18 @@ class SolutionModel {
|
||||
}
|
||||
|
||||
static async vote(domainId: string, psid: ObjectId, uid: number, value: number) {
|
||||
let pssdoc = await document.getStatus(domainId, document.TYPE_PROBLEM_SOLUTION, psid, uid);
|
||||
await document.setStatus(domainId, document.TYPE_PROBLEM_SOLUTION, psid, uid, { vote: value });
|
||||
if (pssdoc) value += -pssdoc.vote;
|
||||
const psdoc = await document.inc(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'vote', value);
|
||||
pssdoc = await document.getStatus(domainId, document.TYPE_PROBLEM_SOLUTION, psid, uid);
|
||||
return [psdoc, pssdoc];
|
||||
const before = await document.setStatus(
|
||||
domainId, document.TYPE_PROBLEM_SOLUTION, psid, uid,
|
||||
{ vote: value }, 'before',
|
||||
);
|
||||
let inc = value;
|
||||
if (before?.vote) inc -= before.vote;
|
||||
return [
|
||||
inc
|
||||
? await document.inc(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'vote', inc)
|
||||
: await document.get(domainId, document.TYPE_PROBLEM_SOLUTION, psid),
|
||||
await document.getStatus(domainId, document.TYPE_PROBLEM_SOLUTION, psid, uid),
|
||||
];
|
||||
}
|
||||
|
||||
static async getListStatus(domainId: string, psids: ObjectId[], uid: number) {
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
"@fontsource/source-code-pro": "^5.0.18",
|
||||
"@fontsource/ubuntu-mono": "^5.0.20",
|
||||
"@hydrooj/utils": "workspace:^",
|
||||
"@sentry/browser": "^8.25.0",
|
||||
"@sentry/webpack-plugin": "^2.22.0",
|
||||
"@sentry/browser": "^8.26.0",
|
||||
"@sentry/webpack-plugin": "^2.22.2",
|
||||
"@simplewebauthn/browser": "9.0.1",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@types/gulp-if": "^0.0.34",
|
||||
@@ -76,7 +76,7 @@
|
||||
"jquery.easing": "^1.4.1",
|
||||
"jquery.transit": "^0.9.12",
|
||||
"matchmedia-polyfill": "^0.3.2",
|
||||
"md-editor-rt": "^4.18.0",
|
||||
"md-editor-rt": "^4.18.1",
|
||||
"mini-css-extract-plugin": "^2.9.0",
|
||||
"moment": "^2.30.1",
|
||||
"monaco-editor": "0.50.0",
|
||||
@@ -141,7 +141,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markdown-it-anchor": "^9.0.1",
|
||||
"markdown-it-attrs": "^4.1.6",
|
||||
"markdown-it-attrs": "^4.2.0",
|
||||
"markdown-it-footnote": "^4.0.0",
|
||||
"markdown-it-imsize": "^2.0.1",
|
||||
"markdown-it-mark": "^4.0.0",
|
||||
|
||||
Reference in new issue
Block a user