feat(server): impl doc history (#5004)
This commit is contained in:
@@ -16,9 +16,10 @@ import { PrismaService } from '../../prisma';
|
||||
import { StorageProvide } from '../../storage';
|
||||
import { DocID } from '../../utils/doc';
|
||||
import { Auth, CurrentUser, Publicable } from '../auth';
|
||||
import { DocManager } from '../doc';
|
||||
import { DocHistoryManager, DocManager } from '../doc';
|
||||
import { UserType } from '../users';
|
||||
import { PermissionService, PublicPageMode } from './permission';
|
||||
import { Permission } from './types';
|
||||
|
||||
@Controller('/api/workspaces')
|
||||
export class WorkspacesController {
|
||||
@@ -28,6 +29,7 @@ export class WorkspacesController {
|
||||
@Inject(StorageProvide) private readonly storage: Storage,
|
||||
private readonly permission: PermissionService,
|
||||
private readonly docManager: DocManager,
|
||||
private readonly historyManager: DocHistoryManager,
|
||||
private readonly prisma: PrismaService
|
||||
) {}
|
||||
|
||||
@@ -104,4 +106,47 @@ export class WorkspacesController {
|
||||
res.send(update);
|
||||
this.logger.debug(`workspaces doc api: ${format(process.hrtime(start))}`);
|
||||
}
|
||||
|
||||
@Get('/:id/docs/:guid/histories/:timestamp')
|
||||
@Auth()
|
||||
async history(
|
||||
@CurrentUser() user: UserType,
|
||||
@Param('id') ws: string,
|
||||
@Param('guid') guid: string,
|
||||
@Param('timestamp') timestamp: string,
|
||||
@Res() res: Response
|
||||
) {
|
||||
const docId = new DocID(guid, ws);
|
||||
let ts;
|
||||
try {
|
||||
const timeNum = parseInt(timestamp);
|
||||
if (Number.isNaN(timeNum)) {
|
||||
throw new Error('Invalid timestamp');
|
||||
}
|
||||
|
||||
ts = new Date(timeNum);
|
||||
} catch (e) {
|
||||
throw new Error('Invalid timestamp');
|
||||
}
|
||||
|
||||
await this.permission.checkPagePermission(
|
||||
docId.workspace,
|
||||
docId.guid,
|
||||
user.id,
|
||||
Permission.Write
|
||||
);
|
||||
|
||||
const history = await this.historyManager.get(
|
||||
docId.workspace,
|
||||
docId.guid,
|
||||
ts
|
||||
);
|
||||
|
||||
if (history) {
|
||||
res.setHeader('content-type', 'application/octet-stream');
|
||||
res.send(history.blob);
|
||||
} else {
|
||||
throw new NotFoundException('Doc history not found');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import {
|
||||
Args,
|
||||
Field,
|
||||
GraphQLISODateTime,
|
||||
Int,
|
||||
Mutation,
|
||||
ObjectType,
|
||||
Parent,
|
||||
ResolveField,
|
||||
Resolver,
|
||||
} from '@nestjs/graphql';
|
||||
import type { SnapshotHistory } from '@prisma/client';
|
||||
|
||||
import { DocID } from '../../utils/doc';
|
||||
import { Auth, CurrentUser } from '../auth';
|
||||
import { DocHistoryManager } from '../doc/history';
|
||||
import { UserType } from '../users';
|
||||
import { PermissionService } from './permission';
|
||||
import { WorkspaceType } from './resolver';
|
||||
import { Permission } from './types';
|
||||
|
||||
@ObjectType()
|
||||
class DocHistoryType implements Partial<SnapshotHistory> {
|
||||
@Field()
|
||||
workspaceId!: string;
|
||||
|
||||
@Field()
|
||||
id!: string;
|
||||
|
||||
@Field(() => GraphQLISODateTime)
|
||||
timestamp!: Date;
|
||||
}
|
||||
|
||||
@Resolver(() => WorkspaceType)
|
||||
export class DocHistoryResolver {
|
||||
constructor(
|
||||
private readonly historyManager: DocHistoryManager,
|
||||
private readonly permission: PermissionService
|
||||
) {}
|
||||
|
||||
@ResolveField(() => [DocHistoryType])
|
||||
async histories(
|
||||
@Parent() workspace: WorkspaceType,
|
||||
@Args('guid') guid: string,
|
||||
@Args({ name: 'before', type: () => GraphQLISODateTime, nullable: true })
|
||||
timestamp: Date = new Date(),
|
||||
@Args({ name: 'take', type: () => Int, nullable: true })
|
||||
take?: number
|
||||
): Promise<DocHistoryType[]> {
|
||||
const docId = new DocID(guid, workspace.id);
|
||||
|
||||
if (docId.isWorkspace) {
|
||||
throw new Error('Invalid guid for listing doc histories.');
|
||||
}
|
||||
|
||||
return this.historyManager
|
||||
.list(workspace.id, docId.guid, timestamp, take)
|
||||
.then(rows =>
|
||||
rows.map(({ timestamp }) => {
|
||||
return {
|
||||
workspaceId: workspace.id,
|
||||
id: docId.guid,
|
||||
timestamp,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Auth()
|
||||
@Mutation(() => Date)
|
||||
async recoverDoc(
|
||||
@CurrentUser() user: UserType,
|
||||
@Args('workspaceId') workspaceId: string,
|
||||
@Args('guid') guid: string,
|
||||
@Args({ name: 'timestamp', type: () => GraphQLISODateTime }) timestamp: Date
|
||||
): Promise<Date> {
|
||||
const docId = new DocID(guid, workspaceId);
|
||||
|
||||
if (docId.isWorkspace) {
|
||||
throw new Error('Invalid guid for recovering doc from history.');
|
||||
}
|
||||
|
||||
await this.permission.checkPagePermission(
|
||||
docId.workspace,
|
||||
docId.guid,
|
||||
user.id,
|
||||
Permission.Write
|
||||
);
|
||||
|
||||
return this.historyManager.recover(docId.workspace, docId.guid, timestamp);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
|
||||
import { DocModule } from '../doc';
|
||||
import { UsersService } from '../users';
|
||||
import { WorkspacesController } from './controller';
|
||||
import { DocHistoryResolver } from './history.resolver';
|
||||
import { PermissionService } from './permission';
|
||||
import { PagePermissionResolver, WorkspaceResolver } from './resolver';
|
||||
|
||||
@@ -14,6 +15,7 @@ import { PagePermissionResolver, WorkspaceResolver } from './resolver';
|
||||
PermissionService,
|
||||
UsersService,
|
||||
PagePermissionResolver,
|
||||
DocHistoryResolver,
|
||||
],
|
||||
exports: [PermissionService],
|
||||
})
|
||||
|
||||
@@ -244,18 +244,20 @@ export class PermissionService {
|
||||
permission = Permission.Read
|
||||
) {
|
||||
// check whether page is public
|
||||
const count = await this.prisma.workspacePage.count({
|
||||
where: {
|
||||
workspaceId: ws,
|
||||
pageId: page,
|
||||
public: true,
|
||||
},
|
||||
});
|
||||
if (permission === Permission.Read) {
|
||||
const count = await this.prisma.workspacePage.count({
|
||||
where: {
|
||||
workspaceId: ws,
|
||||
pageId: page,
|
||||
public: true,
|
||||
},
|
||||
});
|
||||
|
||||
// page is public
|
||||
// accessible
|
||||
if (count > 0) {
|
||||
return true;
|
||||
// page is public
|
||||
// accessible
|
||||
if (count > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (user) {
|
||||
|
||||
Reference in New Issue
Block a user