feat(storage): binding jwst storage to node (#2808)

This commit is contained in:
liuyi
2023-06-29 09:45:45 +08:00
committed by GitHub
parent 86616e152d
commit 2c95bfcc3d
40 changed files with 5621 additions and 98 deletions

View File

@@ -0,0 +1,27 @@
import { Storage } from '@affine/storage';
import { Controller, Get, NotFoundException, Param, Res } from '@nestjs/common';
import type { Response } from 'express';
@Controller('/api/workspaces')
export class WorkspacesController {
constructor(private readonly storage: Storage) {}
@Get('/:id/blobs/:name')
async blob(
@Param('id') workspaceId: string,
@Param('name') name: string,
@Res() res: Response
) {
const blob = await this.storage.blob(workspaceId, name);
if (!blob) {
throw new NotFoundException('Blob not found');
}
res.setHeader('content-type', blob.contentType);
res.setHeader('last-modified', blob.lastModified);
res.setHeader('content-length', blob.size);
res.send(blob.data);
}
}

View File

@@ -1,10 +1,11 @@
import { Module } from '@nestjs/common';
import { WorkspacesController } from './controller';
import { PermissionService } from './permission';
import { WorkspaceResolver } from './resolver';
@Module({
providers: [WorkspaceResolver, PermissionService],
providers: [WorkspaceResolver, PermissionService, WorkspacesController],
exports: [PermissionService],
})
export class WorkspaceModule {}

View File

@@ -1,3 +1,4 @@
import { Storage } from '@affine/storage';
import { ForbiddenException, NotFoundException } from '@nestjs/common';
import {
Args,
@@ -16,8 +17,11 @@ import {
Resolver,
} from '@nestjs/graphql';
import type { User, Workspace } from '@prisma/client';
// @ts-expect-error graphql-upload is not typed
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs';
import { PrismaService } from '../../prisma';
import type { FileUpload } from '../../types';
import { Auth, CurrentUser } from '../auth';
import { UserType } from '../users/resolver';
import { PermissionService } from './permission';
@@ -55,7 +59,8 @@ export class UpdateWorkspaceInput extends PickType(
export class WorkspaceResolver {
constructor(
private readonly prisma: PrismaService,
private readonly permissionProvider: PermissionService
private readonly permissionProvider: PermissionService,
private readonly storage: Storage
) {}
@ResolveField(() => Permission, {
@@ -174,8 +179,25 @@ export class WorkspaceResolver {
@Mutation(() => WorkspaceType, {
description: 'Create a new workspace',
})
async createWorkspace(@CurrentUser() user: User) {
return this.prisma.workspace.create({
async createWorkspace(
@CurrentUser() user: User,
@Args({ name: 'init', type: () => GraphQLUpload })
update: FileUpload
) {
// convert stream to buffer
const buffer = await new Promise<Buffer>((resolve, reject) => {
const stream = update.createReadStream();
const chunks: Uint8Array[] = [];
stream.on('data', chunk => {
chunks.push(chunk);
});
stream.on('error', reject);
stream.on('end', () => {
resolve(Buffer.concat(chunks));
});
});
const workspace = await this.prisma.workspace.create({
data: {
public: false,
users: {
@@ -191,6 +213,10 @@ export class WorkspaceResolver {
},
},
});
await this.storage.createWorkspace(workspace.id, buffer);
return workspace;
}
@Mutation(() => WorkspaceType, {
@@ -221,8 +247,15 @@ export class WorkspaceResolver {
},
});
await this.prisma.userWorkspacePermission.deleteMany({
where: {
workspaceId: id,
},
});
// TODO:
// delete all related data, like websocket connections, blobs, etc.
await this.storage.deleteWorkspace(id);
return true;
}
@@ -283,4 +316,28 @@ export class WorkspaceResolver {
return this.permissionProvider.revoke(workspaceId, user.id);
}
@Mutation(() => String)
async uploadBlob(
@CurrentUser() user: User,
@Args('workspaceId') workspaceId: string,
@Args({ name: 'blob', type: () => GraphQLUpload })
blob: FileUpload
) {
await this.permissionProvider.check(workspaceId, user.id);
const buffer = await new Promise<Buffer>((resolve, reject) => {
const stream = blob.createReadStream();
const chunks: Uint8Array[] = [];
stream.on('data', chunk => {
chunks.push(chunk);
});
stream.on('error', reject);
stream.on('end', () => {
resolve(Buffer.concat(chunks));
});
});
return this.storage.uploadBlob(workspaceId, buffer);
}
}