mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
[ranges-tracker] add legacy orderedRejections field in tracked-changes (#36565)
* [docstore] use more realistic test stubs for ranges * [monorepo] define ranges schemas in @overleaf/ranges-tracker * [ranges-tracker] add legacy orderedRejections field in tracked-changes * [monorepo] resolve @overleaf/ranges-tracker subpath imports in typescript * [real-time] validate comment thread ids and tracked change id seeds * [ranges-tracker] require ObjectId ids and tracked change metadata in ranges * [monorepo] require ObjectId tracked change ids on the accept routes * [web] use ObjectId comment thread ids in the DocumentController tests GitOrigin-RevId: 58b20dec311d85b917ef63f9efbe745a265a2de6
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
const { expressify } = require('@overleaf/promise-utils')
|
||||
const { parseReq, z, zz } = require('@overleaf/validation-tools')
|
||||
const editorCoreSchemas = require('overleaf-editor-core/lib/schemas')
|
||||
const schemas = require('./schemas')
|
||||
const rangesSchemas = require('@overleaf/ranges-tracker/schemas')
|
||||
const DocumentManager = require('./DocumentManager')
|
||||
const HistoryManager = require('./HistoryManager')
|
||||
const ProjectManager = require('./ProjectManager')
|
||||
@@ -487,15 +487,14 @@ async function deleteMultipleProjects(req, res) {
|
||||
res.sendStatus(204) // No Content
|
||||
}
|
||||
|
||||
// tracked-change ids are RangesTracker ids (seed + increment), not ObjectIds
|
||||
const acceptChangesSchema = z.object({
|
||||
params: z.strictObject({
|
||||
project_id: zz.objectId(),
|
||||
doc_id: zz.objectId(),
|
||||
change_id: z.string().min(1).optional(),
|
||||
change_id: zz.objectId().optional(),
|
||||
}),
|
||||
body: z.strictObject({
|
||||
change_ids: z.array(z.string().min(1)).optional(),
|
||||
change_ids: z.array(zz.objectId()).optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -610,7 +609,7 @@ const addUpdateSchema = z.strictObject({
|
||||
id: zz.objectId(),
|
||||
pathname: zz.safePath(),
|
||||
docLines: z.string().optional(),
|
||||
ranges: schemas.ranges.optional(),
|
||||
ranges: rangesSchemas.ranges.optional(),
|
||||
historyRangesSupport: z.boolean().optional(),
|
||||
// legacy filestore url for files without a created blob
|
||||
url: z.string().nullish(),
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
// @ts-check
|
||||
'use strict'
|
||||
|
||||
const { z, zz } = require('@overleaf/validation-tools')
|
||||
|
||||
// Zod schemas for the sharejs-text-ot ranges data (RangesTracker format).
|
||||
// The types in ./types.ts are inferred from these schemas.
|
||||
//
|
||||
// Ranges data is persisted round-trip (docstore <-> web <-> document-updater)
|
||||
// without normalization, so legacy shapes survive on old documents and the
|
||||
// schemas model them explicitly: comments/changes preloaded from web may lack `id`.
|
||||
// Tightening these is tracked as part of moving the shared editor payload
|
||||
// schemas into overleaf-editor-core.
|
||||
//
|
||||
// Old tracked changes created while the fixedRemoveChange flag existed
|
||||
// (removed in https://github.com/overleaf/internal/pull/23993) still carry
|
||||
// it in their op until accepted/rejected.
|
||||
|
||||
const insertOp = z.strictObject({
|
||||
i: z.string(),
|
||||
p: z.number().int().min(0),
|
||||
u: z.boolean().optional(),
|
||||
fixedRemoveChange: z.boolean().optional(),
|
||||
})
|
||||
|
||||
const deleteOp = z.strictObject({
|
||||
d: z.string(),
|
||||
p: z.number().int().min(0),
|
||||
u: z.boolean().optional(),
|
||||
fixedRemoveChange: z.boolean().optional(),
|
||||
})
|
||||
|
||||
const commentOp = z.strictObject({
|
||||
c: z.string(),
|
||||
p: z.number().int().min(0),
|
||||
t: zz.objectId(),
|
||||
u: z.boolean().optional(),
|
||||
// Used by project-history when restoring CommentSnapshots
|
||||
resolved: z.boolean().optional(),
|
||||
})
|
||||
|
||||
const rangeMetadata = z.strictObject({
|
||||
user_id: z.string(),
|
||||
ts: z.string(),
|
||||
})
|
||||
|
||||
const comment = z.strictObject({
|
||||
id: zz.objectId().optional(),
|
||||
op: commentOp,
|
||||
metadata: rangeMetadata.optional(),
|
||||
})
|
||||
|
||||
// tracked-change ids are RangesTracker ids (seed + increment), not ObjectIds
|
||||
const trackedChange = z.strictObject({
|
||||
id: z.string().optional(),
|
||||
op: insertOp.or(deleteOp),
|
||||
metadata: rangeMetadata,
|
||||
})
|
||||
|
||||
const ranges = z.strictObject({
|
||||
comments: z.array(comment).optional(),
|
||||
changes: z.array(trackedChange).optional(),
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
insertOp,
|
||||
deleteOp,
|
||||
commentOp,
|
||||
comment,
|
||||
trackedChange,
|
||||
ranges,
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
RawEditOperation,
|
||||
} from 'overleaf-editor-core/lib/types'
|
||||
import type { z } from '@overleaf/validation-tools'
|
||||
import type * as schemas from './schemas'
|
||||
import type rangesSchemas from '@overleaf/ranges-tracker/schemas'
|
||||
|
||||
export type OTType = 'sharejs-text-ot' | 'history-ot'
|
||||
|
||||
@@ -32,25 +32,25 @@ export type HistoryOTEditOperationUpdate = Omit<Update, 'op'> & {
|
||||
|
||||
export type Op = InsertOp | DeleteOp | CommentOp | RetainOp
|
||||
|
||||
export type InsertOp = z.infer<typeof schemas.insertOp>
|
||||
export type InsertOp = z.infer<typeof rangesSchemas.insertOp>
|
||||
|
||||
export type RetainOp = {
|
||||
r: string
|
||||
p: number
|
||||
}
|
||||
|
||||
export type DeleteOp = z.infer<typeof schemas.deleteOp>
|
||||
export type DeleteOp = z.infer<typeof rangesSchemas.deleteOp>
|
||||
|
||||
export type CommentOp = z.infer<typeof schemas.commentOp>
|
||||
export type CommentOp = z.infer<typeof rangesSchemas.commentOp>
|
||||
|
||||
/**
|
||||
* Ranges record on a document
|
||||
*/
|
||||
export type Ranges = z.infer<typeof schemas.ranges>
|
||||
export type Ranges = z.infer<typeof rangesSchemas.ranges>
|
||||
|
||||
export type Comment = z.infer<typeof schemas.comment>
|
||||
export type Comment = z.infer<typeof rangesSchemas.comment>
|
||||
|
||||
export type TrackedChange = z.infer<typeof schemas.trackedChange>
|
||||
export type TrackedChange = z.infer<typeof rangesSchemas.trackedChange>
|
||||
|
||||
/**
|
||||
* Updates sent to project-history
|
||||
|
||||
@@ -307,9 +307,9 @@ describe('Ranges', function () {
|
||||
historyRangesSupport: true,
|
||||
})
|
||||
await DocUpdaterClient.preloadDoc(this.project_id, this.doc.id)
|
||||
this.id_seed_1 = 'tc_1'
|
||||
this.id_seed_2 = 'tc_2'
|
||||
this.id_seed_3 = 'tc_3'
|
||||
this.id_seed_1 = '587357bd35e64f6151'
|
||||
this.id_seed_2 = '587357bd35e64f6152'
|
||||
this.id_seed_3 = '587357bd35e64f6153'
|
||||
|
||||
this.updates = [
|
||||
{
|
||||
@@ -718,7 +718,7 @@ describe('Ranges', function () {
|
||||
before(async function () {
|
||||
this.project_id = DocUpdaterClient.randomId()
|
||||
this.user_id = DocUpdaterClient.randomId()
|
||||
this.id_seed = DocUpdaterClient.randomId()
|
||||
this.id_seed = '587357bd35e64f6157'
|
||||
this.doc = {
|
||||
id: DocUpdaterClient.randomId(),
|
||||
lines: ['aaa'],
|
||||
|
||||
@@ -11,7 +11,7 @@ const {
|
||||
zz,
|
||||
} = require('@overleaf/validation-tools')
|
||||
const { rawStringFileData } = require('overleaf-editor-core/lib/schemas')
|
||||
const schemas = require('../../../../app/js/schemas')
|
||||
const schemas = require('@overleaf/ranges-tracker/schemas')
|
||||
const app = express()
|
||||
const MAX_REQUEST_SIZE = 2 * (2 * 1024 * 1024 + 64 * 1024)
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
const { expect } = require('chai')
|
||||
const { insertOp, deleteOp } = require('../../../app/js/schemas')
|
||||
|
||||
describe('schemas', function () {
|
||||
describe('insertOp', function () {
|
||||
it('accepts a legacy fixedRemoveChange flag', function () {
|
||||
const result = insertOp.safeParse({
|
||||
i: 'foo',
|
||||
p: 0,
|
||||
fixedRemoveChange: true,
|
||||
})
|
||||
expect(result.success).to.equal(true)
|
||||
})
|
||||
|
||||
it('rejects an unrecognized key', function () {
|
||||
const result = insertOp.safeParse({ i: 'foo', p: 0, somethingElse: true })
|
||||
expect(result.success).to.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteOp', function () {
|
||||
it('accepts a legacy fixedRemoveChange flag', function () {
|
||||
const result = deleteOp.safeParse({
|
||||
d: 'foo',
|
||||
p: 0,
|
||||
fixedRemoveChange: true,
|
||||
})
|
||||
expect(result.success).to.equal(true)
|
||||
})
|
||||
|
||||
it('rejects an unrecognized key', function () {
|
||||
const result = deleteOp.safeParse({ d: 'foo', p: 0, somethingElse: true })
|
||||
expect(result.success).to.equal(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user