first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -0,0 +1,269 @@
'use strict'
const _ = require('lodash')
const paths = _.reduce(
[require('./projects').paths, require('./project_import').paths],
_.extend
)
const securityDefinitions = require('./security_definitions')
module.exports = {
swagger: '2.0',
info: {
title: 'Overleaf Editor API',
description: 'API for the Overleaf editor.',
version: '1.0',
},
produces: ['application/json'],
basePath: '/api',
paths,
securityDefinitions,
security: [
{
jwt: [],
},
],
definitions: {
Project: {
properties: {
projectId: {
type: 'string',
},
},
required: ['projectId'],
},
File: {
properties: {
hash: {
type: 'string',
},
byteLength: {
type: 'integer',
},
stringLength: {
type: 'integer',
},
},
},
Label: {
properties: {
authorId: {
type: 'integer',
},
text: {
type: 'string',
},
timestamp: {
type: 'string',
},
version: {
type: 'integer',
},
},
},
Chunk: {
properties: {
history: {
$ref: '#/definitions/History',
},
startVersion: {
type: 'number',
},
},
},
ChunkResponse: {
properties: {
chunk: {
$ref: '#/definitions/Chunk',
},
authors: {
type: 'array',
items: {
$ref: '#/definitions/Author',
},
},
},
},
ChunkResponseRaw: {
properties: {
startVersion: {
type: 'number',
},
endVersion: {
type: 'number',
},
endTimestamp: {
type: 'string',
},
},
},
History: {
properties: {
snapshot: {
$ref: '#/definitions/Snapshot',
},
changes: {
type: 'array',
items: {
$ref: '#/definitions/Change',
},
},
},
},
Snapshot: {
properties: {
files: {
type: 'object',
additionalProperties: {
$ref: '#/definitions/File',
},
},
},
required: ['files'],
},
Change: {
properties: {
timestamp: {
type: 'string',
},
operations: {
type: 'array',
items: {
$ref: '#/definitions/Operation',
},
},
authors: {
type: 'array',
items: {
type: ['integer', 'null'],
},
},
v2Authors: {
type: 'array',
items: {
type: ['string', 'null'],
},
},
projectVersion: {
type: 'string',
},
v2DocVersions: {
type: 'object',
additionalProperties: {
$ref: '#/definitions/V2DocVersions',
},
},
},
required: ['timestamp', 'operations'],
},
V2DocVersions: {
properties: {
pathname: {
type: 'string',
},
v: {
type: 'integer',
},
},
},
ChangeRequest: {
properties: {
baseVersion: {
type: 'integer',
},
untransformable: {
type: 'boolean',
},
operations: {
type: 'array',
items: {
$ref: '#/definitions/Operation',
},
},
authors: {
type: 'array',
items: {
type: ['integer', 'null'],
},
},
},
required: ['baseVersion', 'operations'],
},
ChangeNote: {
properties: {
baseVersion: {
type: 'integer',
},
change: {
$ref: '#/definitions/Change',
},
},
required: ['baseVersion'],
},
Operation: {
properties: {
pathname: {
type: 'string',
},
newPathname: {
type: 'string',
},
blob: {
$ref: '#/definitions/Blob',
},
textOperation: {
type: 'array',
items: {},
},
file: {
$ref: '#/definitions/File',
},
},
},
Error: {
properties: {
message: {
type: 'string',
},
},
required: ['message'],
},
Blob: {
properties: {
hash: {
type: 'string',
},
},
required: ['hash'],
},
Author: {
properties: {
id: {
type: 'integer',
},
email: {
type: 'string',
},
name: {
type: 'string',
},
},
required: ['id', 'email', 'name'],
},
SyncState: {
properties: {
synced: {
type: 'boolean',
},
},
},
ZipInfo: {
properties: {
zipUrl: {
type: 'string',
},
},
required: ['zipUrl'],
},
},
}

View File

@@ -0,0 +1,147 @@
'use strict'
const importSnapshot = {
'x-swagger-router-controller': 'project_import',
operationId: 'importSnapshot',
tags: ['ProjectImport'],
description: 'Import a snapshot from the current rails app.',
consumes: ['application/json'],
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
{
name: 'snapshot',
in: 'body',
description: 'Snapshot to import.',
required: true,
schema: {
$ref: '#/definitions/Snapshot',
},
},
],
responses: {
200: {
description: 'Imported',
},
409: {
description: 'Conflict: project already initialized',
},
404: {
description: 'No such project exists',
},
},
security: [
{
basic: [],
},
],
}
const importChanges = {
'x-swagger-router-controller': 'project_import',
operationId: 'importChanges',
tags: ['ProjectImport'],
description: 'Import changes for a project from the current rails app.',
consumes: ['application/json'],
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
{
name: 'end_version',
description: 'end_version of latest persisted chunk',
in: 'query',
required: true,
type: 'number',
},
{
name: 'return_snapshot',
description:
'optionally, return a snapshot with the latest hashed content',
in: 'query',
required: false,
type: 'string',
enum: ['hashed', 'none'],
},
{
name: 'changes',
in: 'body',
description: 'changes to be imported',
required: true,
schema: {
type: 'array',
items: {
$ref: '#/definitions/Change',
},
},
},
],
responses: {
201: {
description: 'Created',
schema: {
$ref: '#/definitions/Snapshot',
},
},
},
security: [
{
basic: [],
},
],
}
const getChanges = {
'x-swagger-router-controller': 'projects',
operationId: 'getChanges',
tags: ['Project'],
description: 'Get changes applied to a project',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
{
name: 'since',
in: 'query',
description: 'start version',
required: false,
type: 'number',
},
],
responses: {
200: {
description: 'Success',
schema: {
type: 'array',
items: {
$ref: '#/definitions/Change',
},
},
},
},
security: [
{
basic: [],
},
],
}
exports.paths = {
'/projects/{project_id}/import': { post: importSnapshot },
'/projects/{project_id}/legacy_import': { post: importSnapshot },
'/projects/{project_id}/changes': { get: getChanges, post: importChanges },
'/projects/{project_id}/legacy_changes': { post: importChanges },
}

View File

@@ -0,0 +1,588 @@
'use strict'
const Blob = require('overleaf-editor-core').Blob
exports.paths = {
'/projects': {
post: {
'x-swagger-router-controller': 'projects',
operationId: 'initializeProject',
tags: ['Project'],
description: 'Initialize project.',
consumes: ['application/json'],
parameters: [
{
name: 'body',
in: 'body',
schema: {
type: 'object',
properties: {
projectId: { type: 'string' },
},
},
},
],
responses: {
200: {
description: 'Initialized',
schema: {
$ref: '#/definitions/Project',
},
},
},
security: [
{
basic: [],
},
],
},
},
'/projects/{project_id}': {
delete: {
'x-swagger-router-controller': 'projects',
operationId: 'deleteProject',
tags: ['Project'],
description: "Delete a project's history",
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
],
responses: {
204: {
description: 'Success',
},
},
security: [
{
basic: [],
},
],
},
},
'/projects/{project_id}/blobs/{hash}': {
get: {
'x-swagger-router-controller': 'projects',
operationId: 'getProjectBlob',
tags: ['Project'],
description: 'Fetch blob content by its project id and hash.',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
{
name: 'hash',
in: 'path',
description: 'Hexadecimal SHA-1 hash',
required: true,
type: 'string',
pattern: Blob.HEX_HASH_RX_STRING,
},
{
name: 'range',
in: 'header',
description: 'HTTP Range header',
required: false,
type: 'string',
},
],
produces: ['application/octet-stream'],
responses: {
200: {
description: 'Success',
schema: {
type: 'file',
},
},
404: {
description: 'Not Found',
schema: {
$ref: '#/definitions/Error',
},
},
},
security: [{ jwt: [] }, { token: [] }],
},
head: {
'x-swagger-router-controller': 'projects',
operationId: 'headProjectBlob',
tags: ['Project'],
description: 'Fetch blob content-length by its project id and hash.',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
{
name: 'hash',
in: 'path',
description: 'Hexadecimal SHA-1 hash',
required: true,
type: 'string',
pattern: Blob.HEX_HASH_RX_STRING,
},
],
produces: ['application/octet-stream'],
responses: {
200: {
description: 'Success',
schema: {
type: 'file',
},
},
404: {
description: 'Not Found',
schema: {
$ref: '#/definitions/Error',
},
},
},
security: [{ jwt: [] }, { token: [] }],
},
put: {
'x-swagger-router-controller': 'projects',
operationId: 'createProjectBlob',
tags: ['Project'],
description:
'Create blob to be used in a file addition operation when importing a' +
' snapshot or changes',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
{
name: 'hash',
in: 'path',
description: 'Hexadecimal SHA-1 hash',
required: true,
type: 'string',
pattern: Blob.HEX_HASH_RX_STRING,
},
],
responses: {
201: {
description: 'Created',
},
},
},
post: {
'x-swagger-router-controller': 'projects',
operationId: 'copyProjectBlob',
tags: ['Project'],
description:
'Copies a blob from a source project to a target project when duplicating a project',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'target project id',
required: true,
type: 'string',
},
{
name: 'hash',
in: 'path',
description: 'Hexadecimal SHA-1 hash',
required: true,
type: 'string',
pattern: Blob.HEX_HASH_RX_STRING,
},
{
name: 'copyFrom',
in: 'query',
description: 'source project id',
required: true,
type: 'string',
},
],
responses: {
201: {
description: 'Created',
},
},
},
},
'/projects/{project_id}/latest/content': {
get: {
'x-swagger-router-controller': 'projects',
operationId: 'getLatestContent',
tags: ['Project'],
description:
'Get full content of the latest version. Text file ' +
'content is included, but binary files are just linked by hash.',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
],
responses: {
200: {
description: 'Success',
schema: {
$ref: '#/definitions/Snapshot',
},
},
404: {
description: 'Not Found',
schema: {
$ref: '#/definitions/Error',
},
},
},
},
},
'/projects/{project_id}/latest/hashed_content': {
get: {
'x-swagger-router-controller': 'projects',
operationId: 'getLatestHashedContent',
tags: ['Project'],
description:
'Get a snapshot of a project at the latest version ' +
'with the hashes for the contents each file',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
],
responses: {
200: {
description: 'Success',
schema: {
$ref: '#/definitions/Snapshot',
},
},
404: {
description: 'Not Found',
schema: {
$ref: '#/definitions/Error',
},
},
},
security: [
{
basic: [],
},
],
},
},
'/projects/{project_id}/latest/history': {
get: {
'x-swagger-router-controller': 'projects',
operationId: 'getLatestHistory',
tags: ['Project'],
description:
'Get the latest sequence of changes.' +
' TODO probably want a configurable depth.',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
],
responses: {
200: {
description: 'Success',
schema: {
$ref: '#/definitions/ChunkResponse',
},
},
404: {
description: 'Not Found',
schema: {
$ref: '#/definitions/Error',
},
},
},
},
},
'/projects/{project_id}/latest/history/raw': {
get: {
'x-swagger-router-controller': 'projects',
operationId: 'getLatestHistoryRaw',
tags: ['Project'],
description: 'Get the metadata of latest sequence of changes.',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
{
name: 'readOnly',
in: 'query',
description: 'use read only database connection',
required: false,
type: 'boolean',
},
],
responses: {
200: {
description: 'Success',
schema: {
$ref: '#/definitions/ChunkResponseRaw',
},
},
404: {
description: 'Not Found',
schema: {
$ref: '#/definitions/Error',
},
},
},
},
},
'/projects/{project_id}/latest/persistedHistory': {
get: {
'x-swagger-router-controller': 'projects',
operationId: 'getLatestPersistedHistory',
tags: ['Project'],
description: 'Get the latest sequence of changes.',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
],
responses: {
200: {
description: 'Success',
schema: {
$ref: '#/definitions/ChunkResponse',
},
},
404: {
description: 'Not Found',
schema: {
$ref: '#/definitions/Error',
},
},
},
},
},
'/projects/{project_id}/versions/{version}/history': {
get: {
'x-swagger-router-controller': 'projects',
operationId: 'getHistory',
tags: ['Project'],
description:
'Get the sequence of changes that includes the given version.',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
{
name: 'version',
in: 'path',
description: 'numeric version',
required: true,
type: 'number',
},
],
responses: {
200: {
description: 'Success',
schema: {
$ref: '#/definitions/ChunkResponse',
},
},
404: {
description: 'Not Found',
schema: {
$ref: '#/definitions/Error',
},
},
},
},
},
'/projects/{project_id}/versions/{version}/content': {
get: {
'x-swagger-router-controller': 'projects',
operationId: 'getContentAtVersion',
tags: ['Project'],
description: 'Get full content at the given version',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
{
name: 'version',
in: 'path',
description: 'numeric version',
required: true,
type: 'number',
},
],
responses: {
200: {
description: 'Success',
schema: {
$ref: '#/definitions/Snapshot',
},
},
404: {
description: 'Not Found',
schema: {
$ref: '#/definitions/Error',
},
},
},
},
},
'/projects/{project_id}/timestamp/{timestamp}/history': {
get: {
'x-swagger-router-controller': 'projects',
operationId: 'getHistoryBefore',
tags: ['Project'],
description:
'Get the sequence of changes. ' + ' before the given timestamp',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
{
name: 'timestamp',
in: 'path',
description: 'timestamp',
required: true,
type: 'string',
format: 'date-time',
},
],
responses: {
200: {
description: 'Success',
schema: {
$ref: '#/definitions/ChunkResponse',
},
},
404: {
description: 'Not Found',
schema: {
$ref: '#/definitions/Error',
},
},
},
},
},
'/projects/{project_id}/version/{version}/zip': {
get: {
'x-swagger-router-controller': 'projects',
operationId: 'getZip',
tags: ['Project'],
description: 'Download zip with project content',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
{
name: 'version',
in: 'path',
description: 'numeric version',
required: true,
type: 'number',
},
],
produces: ['application/octet-stream'],
responses: {
200: {
description: 'success',
},
404: {
description: 'not found',
},
},
security: [
{
token: [],
},
],
},
post: {
'x-swagger-router-controller': 'projects',
operationId: 'createZip',
tags: ['Project'],
description:
'Create a zip file with project content. Returns a link to be polled.',
parameters: [
{
name: 'project_id',
in: 'path',
description: 'project id',
required: true,
type: 'string',
},
{
name: 'version',
in: 'path',
description: 'numeric version',
required: true,
type: 'number',
},
],
responses: {
200: {
description: 'success',
schema: {
$ref: '#/definitions/ZipInfo',
},
},
404: {
description: 'not found',
},
},
security: [
{
basic: [],
},
],
},
},
}

View File

@@ -0,0 +1,17 @@
'use strict'
module.exports = {
jwt: {
type: 'apiKey',
in: 'header',
name: 'authorization',
},
basic: {
type: 'basic',
},
token: {
type: 'apiKey',
in: 'query',
name: 'token',
},
}