{"record":{"id":"1541d0bb997fb9ac","repo":"hcengineering/platform","slug":"direct-modifications-of-ratings-are-not-allowed","errorCode":null,"errorMessage":"Direct modifications of ratings are not allowed.","messagePattern":"Direct modifications of ratings are not allowed\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server-plugins/rating/src/index.ts","lineNumber":53,"sourceCode":"} from '@hcengineering/server-core'\n\n/**\n * @public\n */\nexport const serverRatingId = 'server-rating' as Plugin\n\nexport class RatingMiddleware extends BaseMiddleware {\n  static async create (ctx: MeasureContext, context: PipelineContext, next?: Middleware): Promise<Middleware> {\n    return new RatingMiddleware(context, next)\n  }\n\n  async tx (ctx: MeasureContext, tx: Tx[]): Promise<TxMiddlewareResult> {\n    for (const t of tx) {\n      if (TxProcessor.isExtendsCUD(t._class)) {\n        const cud = t as TxCUD<Doc>\n\n        if (cud.objectClass === rating.class.DocRating || cud.objectClass === rating.class.PersonRating) {\n          throw new Error('Direct modifications of ratings are not allowed.')\n        }\n        const c = rating.class.DocReaction\n        if (cud.objectClass === c) {\n          // Star/Like,Rate values could only be used one per user.\n          switch (cud._class) {\n            case core.class.TxCreateDoc:\n              // Check for duplicate of like, rate value, Emojii\n              await this.validateNewReaction(ctx, cud as TxCreateDoc<DocReaction>)\n              break\n            case core.class.TxUpdateDoc:\n              // Disallow update for Emojii, Star, Like, Usefull\n              // Allow only for RateValue\n              await this.validateReactionUpdate(ctx, cud as TxUpdateDoc<DocReaction>)\n              break\n            case core.class.TxRemoveDoc:\n              await this.validateReactionRemove(ctx, cud as TxRemoveDoc<DocReaction>)\n              break\n          }","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/server-plugins/rating/src/index.ts#L35-L71","documentation":"The rating plugin's tx middleware intercepts CUD transactions and forbids any direct create/update/delete of rating.class.DocRating or rating.class.PersonRating documents. Ratings must be derived through the plugin's own flow (e.g. DocReaction transactions), so direct writes are rejected to keep aggregates consistent.","triggerScenarios":"Any TxCreateDoc/TxUpdateDoc/TxRemoveDoc with objectClass DocRating or PersonRating submitted through the transactor while this plugin is registered — e.g. application code or a client calling createDoc/updateDoc on those classes directly.","commonSituations":"Client UI writing rating docs directly, migration/import scripts copying rating rows, tests seeding rating documents via generic CRUD instead of the plugin API.","solutions":["Stop writing DocRating/PersonRating directly; use the plugin's reaction API (create DocReaction via the supported flow).","Refactor migrations/seeds to produce reactions (or compute ratings offline) instead of inserting rating documents.","Remove or gate any client-side code that calls createDoc/updateDoc/removeDoc on rating classes.","If you truly need backfills, do them in a context that bypasses/registers before this middleware, understanding aggregate consistency is then your responsibility."],"exampleFix":"// before\nawait client.createDoc(rating.class.DocRating, space, { ... })\n// after\nawait client.createDoc(rating.class.DocReaction, space, {\n  attachedTo: docId,\n  reactionType: ReactionKind.Like,\n  value: 1\n})","handlingStrategy":"validation","validationCode":"const FORBIDDEN = [rating.class.DocRating, rating.class.PersonRating]\nif (FORBIDDEN.includes(tx.objectClass)) {\n  throw new Error('Use DocReaction instead of writing rating docs directly')\n}","typeGuard":"function isDirectRatingWrite(t: TxCUD<Doc>): boolean {\n  return t.objectClass === rating.class.DocRating || t.objectClass === rating.class.PersonRating\n}","tryCatchPattern":"try {\n  await client.createDoc(target.class, space, data)\n} catch (err) {\n  if (/Direct modifications of ratings/.test(err.message)) {\n    console.error('Rating docs are derived; use the DocReaction API')\n  }\n  throw err\n}","preventionTips":["Only create/modify DocReaction documents, never DocRating/PersonRating.","Audit migrations and import scripts for writes to rating classes.","Expose only reaction endpoints to clients.","Add lint/tests asserting no direct rating-class CRUD."],"tags":["rating","transactions","validation","plugin"],"backgroundTag":"direct-write-forbidden","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}