Support for rating system (#10124)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2025-10-24 10:10:01 +07:00
committed by GitHub
parent 768ff39ab1
commit ffd8d90378
137 changed files with 5005 additions and 712 deletions
+139
View File
@@ -0,0 +1,139 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import core, {
TxProcessor,
type Doc,
type MeasureContext,
type Tx,
type TxCreateDoc,
type TxCUD,
type TxRemoveDoc,
type TxUpdateDoc
} from '@hcengineering/core'
import type { Plugin } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import rating, { ReactionKind, type DocReaction } from '@hcengineering/rating'
import {
BaseMiddleware,
type Middleware,
type PipelineContext,
type TxMiddlewareResult
} from '@hcengineering/server-core'
/**
* @public
*/
export const serverRatingId = 'server-rating' as Plugin
export class RatingMiddleware extends BaseMiddleware {
static async create (ctx: MeasureContext, context: PipelineContext, next?: Middleware): Promise<Middleware> {
return new RatingMiddleware(context, next)
}
async tx (ctx: MeasureContext, tx: Tx[]): Promise<TxMiddlewareResult> {
for (const t of tx) {
if (TxProcessor.isExtendsCUD(t._class)) {
const cud = t as TxCUD<Doc>
if (cud.objectClass === rating.class.DocRating || cud.objectClass === rating.class.PersonRating) {
throw new Error('Direct modifications of ratings are not allowed.')
}
const c = rating.class.DocReaction
if (cud.objectClass === c) {
// Star/Like,Rate values could only be used one per user.
switch (cud._class) {
case core.class.TxCreateDoc:
// Check for duplicate of like, rate value, Emojii
await this.validateNewReaction(ctx, cud as TxCreateDoc<DocReaction>)
break
case core.class.TxUpdateDoc:
// Disallow update for Emojii, Star, Like, Usefull
// Allow only for RateValue
await this.validateReactionUpdate(ctx, cud as TxUpdateDoc<DocReaction>)
break
case core.class.TxRemoveDoc:
await this.validateReactionRemove(ctx, cud as TxRemoveDoc<DocReaction>)
break
}
}
}
}
return await this.provideTx(ctx, tx)
}
private async validateNewReaction (ctx: MeasureContext, create: TxCreateDoc<DocReaction>): Promise<void> {
if (create.attributes.reactionType !== ReactionKind.Star) {
// Should allow only one reaction per document per user for Emoji, Like, Usefull
const current = await this.provideFindAll(ctx, rating.class.DocReaction, {
attachedTo: create.attachedTo,
attachedToClass: create.objectClass
})
if (
current.some(
(it) =>
it.reactionType === create.attributes.reactionType &&
(it.value === create.attributes.value ||
(create.attributes.reactionType === ReactionKind.Emoji && it.emoji === create.attributes.emoji))
)
) {
throw new Error('Duplicate emoji reaction is not allowed.')
}
} else {
// For stars, we probable need to merge them into one value.
}
}
private async validateReactionUpdate (ctx: MeasureContext, upd: TxUpdateDoc<DocReaction>): Promise<void> {
if (upd.operations.reactionType !== undefined) {
throw new Error('Modifications of reaction type are not allowed.')
}
// Find current reaction tried to be modified
const current = (
await this.provideFindAll(ctx, rating.class.DocReaction, {
_id: upd.objectId,
_class: upd.objectClass
})
).shift()
if (current === undefined) {
throw new Error('Reaction not found.')
}
if (upd.operations.value === undefined || upd.operations.value < 0 || upd.operations.value > 10) {
throw new Error('Reaction value modification is required.')
}
}
private async validateReactionRemove (ctx: MeasureContext, upd: TxRemoveDoc<DocReaction>): Promise<void> {
// Find current reaction tried to be modified
// const current = (
// await this.provideFindAll(ctx, rating.class.DocReaction, {
// _id: upd.objectId,
// _class: upd.objectClass
// })
// ).shift()
// if (current === undefined) {
// No error, already removed
// }
// if (current.reactionType === ReactionKind.Star) {
// throw new Error('Star reactions could not be removed.')
// }
}
}
/**
* @public
*/
export default plugin(serverRatingId, {})