299 lines
9.2 KiB
TypeScript
299 lines
9.2 KiB
TypeScript
import { Inject, Injectable, Logger } from '@nestjs/common'
|
|
import {
|
|
Pothos,
|
|
PothosRef,
|
|
PothosSchema,
|
|
SchemaBuilderToken,
|
|
} from '@smatch-corp/nestjs-pothos'
|
|
import { Builder, SchemaContext } from '../Graphql/graphql.builder'
|
|
import { PrismaService } from '../Prisma/prisma.service'
|
|
import { DocumentEvent } from './document.event'
|
|
import { Document } from '@prisma/client'
|
|
import { DocumentDelta } from './document.type'
|
|
import Delta from 'quill-delta'
|
|
import { MinioService } from 'src/Minio/minio.service'
|
|
@Injectable()
|
|
export class DocumentSchema extends PothosSchema {
|
|
constructor(
|
|
@Inject(SchemaBuilderToken) private readonly builder: Builder,
|
|
private readonly prisma: PrismaService,
|
|
private readonly minio: MinioService,
|
|
) {
|
|
super()
|
|
}
|
|
|
|
@PothosRef()
|
|
document() {
|
|
return this.builder.prismaObject('Document', {
|
|
fields: (t) => ({
|
|
id: t.exposeID('id'),
|
|
name: t.exposeString('name'),
|
|
fileUrl: t.exposeString('fileUrl'),
|
|
createdAt: t.expose('createdAt', { type: 'DateTime' }),
|
|
updatedAt: t.expose('updatedAt', { type: 'DateTime' }),
|
|
owner: t.relation('owner'),
|
|
ownerId: t.exposeID('ownerId'),
|
|
collaborators: t.relation('collaborators'),
|
|
isPublic: t.exposeBoolean('isPublic'),
|
|
}),
|
|
})
|
|
}
|
|
|
|
@PothosRef()
|
|
documentCollaborator() {
|
|
return this.builder.prismaObject('DocumentCollaborator', {
|
|
fields: (t) => ({
|
|
documentId: t.exposeID('documentId', { nullable: false }),
|
|
userId: t.exposeID('userId', { nullable: false }),
|
|
document: t.relation('document', { nullable: false }),
|
|
user: t.relation('user', { nullable: false }),
|
|
readable: t.exposeBoolean('readable', { nullable: false }),
|
|
writable: t.exposeBoolean('writable', { nullable: false }),
|
|
}),
|
|
})
|
|
}
|
|
|
|
@PothosRef()
|
|
documentDelta() {
|
|
return this.builder.simpleObject('DocumentDelta', {
|
|
fields: (t) => ({
|
|
eventType: t.string(),
|
|
documentId: t.string({
|
|
nullable: true,
|
|
}),
|
|
pageIndex: t.int({
|
|
nullable: true,
|
|
}),
|
|
delta: t.field({
|
|
type: 'Delta',
|
|
nullable: true,
|
|
}),
|
|
senderId: t.string({
|
|
nullable: true,
|
|
}),
|
|
}),
|
|
})
|
|
}
|
|
|
|
@PothosRef()
|
|
documentDeltaInput() {
|
|
return this.builder.inputType('DocumentDeltaInput', {
|
|
fields: (t) => ({
|
|
documentId: t.string(),
|
|
pageIndex: t.int(),
|
|
delta: t.field({ type: 'Delta' }),
|
|
}),
|
|
})
|
|
}
|
|
|
|
@Pothos()
|
|
init(): void {
|
|
this.builder.queryFields((t) => ({
|
|
myDocuments: t.prismaField({
|
|
type: [this.document()],
|
|
args: this.builder.generator.findManyArgs('Document'),
|
|
resolve: async (query, _parent, _args, ctx: SchemaContext) => {
|
|
if (ctx.isSubscription) throw new Error('Not allowed')
|
|
if (!ctx.http?.me?.id) throw new Error('User not found')
|
|
return await this.prisma.document.findMany({
|
|
...query,
|
|
where: {
|
|
ownerId: ctx.http?.me?.id,
|
|
},
|
|
})
|
|
},
|
|
}),
|
|
document: t.prismaField({
|
|
type: this.document(),
|
|
args: this.builder.generator.findUniqueArgs('Document'),
|
|
resolve: async (query, _root, args) => {
|
|
return await this.prisma.document.findUnique({
|
|
...query,
|
|
where: args.where,
|
|
})
|
|
},
|
|
}),
|
|
|
|
documents: t.prismaField({
|
|
type: [this.document()],
|
|
args: this.builder.generator.findManyArgs('Document'),
|
|
resolve: async (query, _root, args) => {
|
|
return await this.prisma.document.findMany({
|
|
...query,
|
|
skip: args.skip ?? undefined,
|
|
take: args.take ?? undefined,
|
|
orderBy: args.orderBy ?? undefined,
|
|
where: args.filter ?? undefined,
|
|
})
|
|
},
|
|
}),
|
|
newDocument: t.field({
|
|
type: this.document(),
|
|
args: {},
|
|
resolve: async (query, _args, ctx, _info) => {
|
|
if (ctx.isSubscription) throw new Error('Not allowed')
|
|
const userId = ctx.http?.me?.id
|
|
if (!userId) throw new Error('User not found')
|
|
const fileUrl = await this.minio.getFileUrl(
|
|
'document',
|
|
'document',
|
|
'document',
|
|
)
|
|
if (!fileUrl) throw new Error('File not found')
|
|
const document = await this.prisma.document.create({
|
|
...query,
|
|
data: {
|
|
name: 'Untitled',
|
|
fileUrl,
|
|
ownerId: userId,
|
|
},
|
|
})
|
|
return document
|
|
},
|
|
}),
|
|
}))
|
|
|
|
this.builder.mutationFields((t) => ({
|
|
createDocument: t.prismaField({
|
|
type: this.document(),
|
|
args: {
|
|
input: t.arg({
|
|
type: this.builder.generator.getCreateInput('Document', [
|
|
'id',
|
|
'ownerId',
|
|
'createdAt',
|
|
'updatedAt',
|
|
'collaborators',
|
|
'owner',
|
|
'fileUrl',
|
|
'previewImageUrl',
|
|
'name',
|
|
]),
|
|
required: false,
|
|
}),
|
|
},
|
|
resolve: async (query, _parent, args, ctx: SchemaContext) => {
|
|
if (ctx.isSubscription) throw new Error('Not allowed')
|
|
const userId = ctx.http?.me?.id
|
|
if (!userId) throw new Error('User not found')
|
|
return await this.prisma.document.create({
|
|
...query,
|
|
data: {
|
|
...args.input,
|
|
name: args.input?.name ?? 'Untitled',
|
|
fileUrl: '',
|
|
owner: {
|
|
connect: {
|
|
id: userId,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
},
|
|
}),
|
|
|
|
testUpdateDocument: t.field({
|
|
type: this.documentDelta(),
|
|
args: {
|
|
documentId: t.arg({ type: 'String', required: true }),
|
|
pageIndex: t.arg({ type: 'Int', required: true }),
|
|
},
|
|
resolve: async (_root, args, ctx: SchemaContext) => {
|
|
if (ctx.isSubscription) throw new Error('Not allowed')
|
|
const delta = new Delta().insert('test')
|
|
const documentDelta = {
|
|
documentId: args.documentId,
|
|
pageIndex: args.pageIndex,
|
|
delta,
|
|
senderId: ctx.http?.me?.id,
|
|
}
|
|
ctx.http.pubSub.publish(
|
|
`${DocumentEvent.CHANGED}.${args.documentId}`,
|
|
documentDelta,
|
|
)
|
|
return documentDelta
|
|
},
|
|
}),
|
|
|
|
updateDocument: t.field({
|
|
type: this.documentDelta(),
|
|
args: {
|
|
data: t.arg({
|
|
type: this.documentDeltaInput(),
|
|
required: true,
|
|
}),
|
|
},
|
|
resolve: async (_, args, ctx: SchemaContext) => {
|
|
if (ctx.isSubscription) throw new Error('Not allowed')
|
|
const {
|
|
http: { pubSub },
|
|
} = ctx
|
|
const senderId = ctx.http?.me?.id
|
|
if (!senderId) throw new Error('User not found')
|
|
pubSub.publish(`${DocumentEvent.CHANGED}.${args.data.documentId}`, {
|
|
...args.data,
|
|
senderId,
|
|
})
|
|
return args.data
|
|
},
|
|
}),
|
|
addCollaborator: t.prismaField({
|
|
type: this.documentCollaborator(),
|
|
args: {
|
|
documentId: t.arg({ type: 'String', required: true }),
|
|
userId: t.arg({ type: 'String', required: true }),
|
|
readable: t.arg({ type: 'Boolean', required: true }),
|
|
writable: t.arg({ type: 'Boolean', required: true }),
|
|
},
|
|
resolve: async (_, __, args, ctx: SchemaContext) => {
|
|
if (ctx.isSubscription) throw new Error('Not allowed')
|
|
// check if ctx user is owner of document
|
|
const document = await this.prisma.document.findUnique({
|
|
where: { id: args.documentId },
|
|
})
|
|
if (!document) throw new Error('Document not found')
|
|
if (document.ownerId !== ctx.http?.me?.id)
|
|
throw new Error('User is not owner of document')
|
|
return await this.prisma.documentCollaborator.create({
|
|
data: {
|
|
documentId: args.documentId,
|
|
userId: args.userId,
|
|
readable: args.readable,
|
|
writable: args.writable,
|
|
},
|
|
})
|
|
},
|
|
}),
|
|
}))
|
|
|
|
this.builder.subscriptionFields((t) => ({
|
|
document: t.field({
|
|
type: this.documentDelta(),
|
|
args: {
|
|
documentId: t.arg({
|
|
type: 'String',
|
|
required: true,
|
|
}),
|
|
},
|
|
subscribe: (_, args, ctx: SchemaContext) => {
|
|
if (!ctx.isSubscription) throw new Error('Not allowed')
|
|
const {
|
|
websocket: { pubSub },
|
|
} = ctx
|
|
return pubSub.asyncIterator([
|
|
`${DocumentEvent.CHANGED}.${args.documentId}`,
|
|
`${DocumentEvent.CREATED}.${args.documentId}`,
|
|
`${DocumentEvent.DELETED}.${args.documentId}`,
|
|
`${DocumentEvent.SAVED}.${args.documentId}`,
|
|
]) as unknown as AsyncIterable<DocumentDelta>
|
|
},
|
|
resolve: async (payload: DocumentDelta, _args, ctx: SchemaContext) => {
|
|
if (!ctx.isSubscription) throw new Error('Not allowed')
|
|
if (payload.senderId === ctx.websocket?.me?.id) return
|
|
return payload
|
|
},
|
|
}),
|
|
}))
|
|
}
|
|
}
|