From 1b05ed82cd55f729f5dcbdb54f016e5d2337bb6a Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 22 Jan 2024 19:50:32 +0100 Subject: [PATCH] feat: added mutations and queries for document drive push and pull transmitter --- api/src/generated/nexus.ts | 19 +- api/src/generated/schema.graphql | 6 +- api/src/modules/DocumentDrive/definitions.ts | 198 +++++++++--------- api/src/modules/DocumentDrive/model.ts | 91 ++++++-- .../DocumentDrive/mutations/addDrive.ts | 12 +- .../DocumentDrive/mutations/addListener.ts | 0 .../DocumentDrive/mutations/pushUpdates.ts | 47 +++-- .../mutations/registerListener.ts | 0 .../DocumentDrive/mutations/updateListener.ts | 0 .../DocumentDrive/queries/pullUpdates.ts | 94 +++++++-- 10 files changed, 288 insertions(+), 179 deletions(-) delete mode 100644 api/src/modules/DocumentDrive/mutations/addListener.ts delete mode 100644 api/src/modules/DocumentDrive/mutations/registerListener.ts delete mode 100644 api/src/modules/DocumentDrive/mutations/updateListener.ts diff --git a/api/src/generated/nexus.ts b/api/src/generated/nexus.ts index bf625b41..490701c6 100644 --- a/api/src/generated/nexus.ts +++ b/api/src/generated/nexus.ts @@ -134,7 +134,7 @@ export interface NexusGenObjects { driveId: string; // String! revision: number; // Int! scope: string; // String! - status: string; // String! + status: NexusGenEnums['UpdateStatus']; // UpdateStatus! } Mutation: {}; Node: { // root type @@ -231,9 +231,10 @@ export interface NexusGenFieldTypes { driveId: string; // String! revision: number; // Int! scope: string; // String! - status: string; // String! + status: NexusGenEnums['UpdateStatus']; // UpdateStatus! } Mutation: { // field return type + acknowledge: boolean | null; // Boolean addDrive: NexusGenRootTypes['AddDriveResponse'] | null; // AddDriveResponse createChallenge: NexusGenRootTypes['Challenge'] | null; // Challenge createSession: NexusGenRootTypes['SessionOutput'] | null; // SessionOutput @@ -257,7 +258,6 @@ export interface NexusGenFieldTypes { stateHash: string; // String! } Query: { // field return type - acknowledge: boolean | null; // Boolean coreUnit: NexusGenRootTypes['CoreUnit'] | null; // CoreUnit coreUnits: Array | null; // [CoreUnit] countUsers: NexusGenRootTypes['Counter'] | null; // Counter @@ -337,9 +337,10 @@ export interface NexusGenFieldTypeNames { driveId: 'String' revision: 'Int' scope: 'String' - status: 'String' + status: 'UpdateStatus' } Mutation: { // field return type name + acknowledge: 'Boolean' addDrive: 'AddDriveResponse' createChallenge: 'Challenge' createSession: 'SessionOutput' @@ -363,7 +364,6 @@ export interface NexusGenFieldTypeNames { stateHash: 'String' } Query: { // field return type name - acknowledge: 'Boolean' coreUnit: 'CoreUnit' coreUnits: 'CoreUnit' countUsers: 'Counter' @@ -404,6 +404,10 @@ export interface NexusGenFieldTypeNames { export interface NexusGenArgTypes { Mutation: { + acknowledge: { // args + listenerId?: string | null; // ID + revisions?: Array | null; // [ListenerRevisionInput] + } addDrive: { // args global: NexusGenInputs['DocumentDriveStateInput']; // DocumentDriveStateInput! local: NexusGenInputs['DocumentDriveLocalStateInput']; // DocumentDriveLocalStateInput! @@ -429,10 +433,6 @@ export interface NexusGenArgTypes { } } Query: { - acknowledge: { // args - listenerId?: string | null; // ID - revisions?: Array | null; // [ListenerRevisionInput] - } coreUnit: { // args id?: string | null; // String } @@ -444,7 +444,6 @@ export interface NexusGenArgTypes { } strands: { // args listenerId?: string | null; // ID - revisions?: Array | null; // [ListenerRevisionInput] } strandsSince: { // args listenerId?: string | null; // ID diff --git a/api/src/generated/schema.graphql b/api/src/generated/schema.graphql index 482356b9..37d15409 100644 --- a/api/src/generated/schema.graphql +++ b/api/src/generated/schema.graphql @@ -80,7 +80,7 @@ type ListenerRevision { driveId: String! revision: Int! scope: String! - status: String! + status: UpdateStatus! } input ListenerRevisionInput { @@ -93,6 +93,7 @@ input ListenerRevisionInput { } type Mutation { + acknowledge(listenerId: ID, revisions: [ListenerRevisionInput]): Boolean addDrive(global: DocumentDriveStateInput!, local: DocumentDriveLocalStateInput!): AddDriveResponse createChallenge(address: String!): Challenge createSession(session: SessionInput!): SessionOutput @@ -119,7 +120,6 @@ type OperationUpdate { } type Query { - acknowledge(listenerId: ID, revisions: [ListenerRevisionInput]): Boolean coreUnit(id: String): CoreUnit coreUnits: [CoreUnit] countUsers(message: String!): Counter @@ -127,7 +127,7 @@ type Query { drives: [String] me: User sessions: [Session] - strands(listenerId: ID, revisions: [ListenerRevisionInput]): [StrandUpdate] + strands(listenerId: ID): [StrandUpdate] strandsSince(listenerId: ID, since: Date): [StrandUpdate] } diff --git a/api/src/modules/DocumentDrive/definitions.ts b/api/src/modules/DocumentDrive/definitions.ts index ade51151..18bb7570 100644 --- a/api/src/modules/DocumentDrive/definitions.ts +++ b/api/src/modules/DocumentDrive/definitions.ts @@ -9,210 +9,210 @@ import { objectType, stringArg, unionType, -} from 'nexus'; +} from "nexus"; export const DocumentDriveLocalState = objectType({ - name: 'DocumentDriveLocalState', + name: "DocumentDriveLocalState", definition(t) { - t.string('sharingType'); - t.nonNull.boolean('availableOffline'); + t.string("sharingType"); + t.nonNull.boolean("availableOffline"); }, }); export const DocumentDriveLocalStateInput = inputObjectType({ - name: 'DocumentDriveLocalStateInput', + name: "DocumentDriveLocalStateInput", definition(t) { - t.string('sharingType'); - t.nonNull.boolean('availableOffline'); + t.string("sharingType"); + t.nonNull.boolean("availableOffline"); }, }); export const DocumentDriveStateInput = inputObjectType({ - name: 'DocumentDriveStateInput', + name: "DocumentDriveStateInput", definition(t) { - t.nonNull.id('id'); - t.nonNull.string('name'); - t.string('icon'); - t.string('remoteUrl'); + t.nonNull.id("id"); + t.nonNull.string("name"); + t.string("icon"); + t.string("remoteUrl"); }, }); export const AddFileInput = inputObjectType({ - name: 'AddFileInput', + name: "AddFileInput", definition(t) { - t.nonNull.id('id'); - t.nonNull.string('name'); - t.nonNull.string('documentType'); - t.id('parentFolder'); + t.nonNull.id("id"); + t.nonNull.string("name"); + t.nonNull.string("documentType"); + t.id("parentFolder"); }, }); export const AddFolderInput = inputObjectType({ - name: 'AddFolderInput', + name: "AddFolderInput", definition(t) { - t.nonNull.id('id'); - t.nonNull.string('name'); - t.id('parentFolder'); + t.nonNull.id("id"); + t.nonNull.string("name"); + t.id("parentFolder"); }, }); export const CopyNodeInput = inputObjectType({ - name: 'CopyNodeInput', + name: "CopyNodeInput", definition(t) { - t.nonNull.id('srcId'); - t.nonNull.id('targetId'); - t.string('targetName'); - t.id('targetParentFolder'); + t.nonNull.id("srcId"); + t.nonNull.id("targetId"); + t.string("targetName"); + t.id("targetParentFolder"); }, }); export const DeleteNodeInput = inputObjectType({ - name: 'DeleteNodeInput', + name: "DeleteNodeInput", definition(t) { - t.nonNull.id('id'); + t.nonNull.id("id"); }, }); export const MoveNodeInput = inputObjectType({ - name: 'MoveNodeInput', + name: "MoveNodeInput", definition(t) { - t.nonNull.id('srcFolder'); - t.id('targetParentFolder'); + t.nonNull.id("srcFolder"); + t.id("targetParentFolder"); }, }); export const SetSharingTypeInput = inputObjectType({ - name: 'SetSharingTypeInput', + name: "SetSharingTypeInput", definition(t) { - t.nonNull.string('type'); + t.nonNull.string("type"); }, }); export const SetAvailableOfflineInput = inputObjectType({ - name: 'SetAvailableOfflineInput', + name: "SetAvailableOfflineInput", definition(t) { - t.nonNull.boolean('availableOffline'); + t.nonNull.boolean("availableOffline"); }, }); export const SetDriveNameInput = inputObjectType({ - name: 'SetDriveNameInput', + name: "SetDriveNameInput", definition(t) { - t.nonNull.string('name'); + t.nonNull.string("name"); }, }); export const UpdateFileInput = inputObjectType({ - name: 'UpdateFileInput', + name: "UpdateFileInput", definition(t) { - t.nonNull.id('id'); - t.id('parentFolder'); - t.string('name'); - t.string('documentType'); + t.nonNull.id("id"); + t.id("parentFolder"); + t.string("name"); + t.string("documentType"); }, }); export const UpdateNodeInput = inputObjectType({ - name: 'UpdateNodeInput', + name: "UpdateNodeInput", definition(t) { - t.nonNull.id('id'); - t.id('parentFolder'); - t.string('name'); + t.nonNull.id("id"); + t.id("parentFolder"); + t.string("name"); }, }); export const Node = objectType({ - name: 'Node', + name: "Node", definition(t) { - t.nonNull.string('id'); - t.nonNull.string('name'); - t.nonNull.string('kind'); - t.string('documentType'); - t.string('parentFolder'); + t.nonNull.string("id"); + t.nonNull.string("name"); + t.nonNull.string("kind"); + t.string("documentType"); + t.string("parentFolder"); }, }); export const DocumentDriveState = objectType({ - name: 'DocumentDriveState', + name: "DocumentDriveState", definition(t) { - t.nonNull.id('id'); - t.nonNull.string('name'); - t.nonNull.list.field('nodes', { type: Node }); - t.string('icon'); - t.string('remoteUrl'); + t.nonNull.id("id"); + t.nonNull.string("name"); + t.nonNull.list.field("nodes", { type: Node }); + t.string("icon"); + t.string("remoteUrl"); }, }); // v2 export const ListenerRevisionInput = inputObjectType({ - name: 'ListenerRevisionInput', + name: "ListenerRevisionInput", definition(t) { - t.nonNull.string('driveId'); - t.nonNull.string('documentId'); - t.nonNull.string('scope'); - t.nonNull.string('branch'); - t.nonNull.field('status', { type: UpdateStatus }); - t.nonNull.int('revision'); + t.nonNull.string("driveId"); + t.nonNull.string("documentId"); + t.nonNull.string("scope"); + t.nonNull.string("branch"); + t.nonNull.field("status", { type: UpdateStatus }); + t.nonNull.int("revision"); }, }); export const ListenerRevision = objectType({ - name: 'ListenerRevision', + name: "ListenerRevision", definition(t) { - t.nonNull.string('driveId'); - t.nonNull.string('documentId'); - t.nonNull.string('scope'); - t.nonNull.string('branch'); - t.nonNull.string('status'); - t.nonNull.int('revision'); + t.nonNull.string("driveId"); + t.nonNull.string("documentId"); + t.nonNull.string("scope"); + t.nonNull.string("branch"); + t.nonNull.field("status", { type: UpdateStatus }); + t.nonNull.int("revision"); }, }); export const OperationUpdate = objectType({ - name: 'OperationUpdate', + name: "OperationUpdate", definition(t) { - t.nonNull.int('revision'); - t.nonNull.int('skip'); - t.nonNull.string('name'); - t.nonNull.string('inputJson'); - t.nonNull.string('stateHash'); + t.nonNull.int("revision"); + t.nonNull.int("skip"); + t.nonNull.string("name"); + t.nonNull.string("inputJson"); + t.nonNull.string("stateHash"); }, }); export const InputOperationUpdate = inputObjectType({ - name: 'InputOperationUpdate', + name: "InputOperationUpdate", definition(t) { - t.nonNull.int('index'); - t.int('skip'); - t.nonNull.string('type'); - t.nonNull.string('input'); - t.nonNull.string('hash'); - t.nonNull.string('timestamp'); + t.nonNull.int("index"); + t.int("skip"); + t.nonNull.string("type"); + t.nonNull.string("input"); + t.nonNull.string("hash"); + t.nonNull.string("timestamp"); }, }); export const StrandUpdate = objectType({ - name: 'StrandUpdate', + name: "StrandUpdate", definition(t) { - t.nonNull.string('driveId'); - t.nonNull.string('documentId'); - t.nonNull.string('scope'); - t.nonNull.string('branch'); - t.nonNull.list.nonNull.field('operations', { type: OperationUpdate }); + t.nonNull.string("driveId"); + t.nonNull.string("documentId"); + t.nonNull.string("scope"); + t.nonNull.string("branch"); + t.nonNull.list.nonNull.field("operations", { type: OperationUpdate }); }, }); export const InputStrandUpdate = inputObjectType({ - name: 'InputStrandUpdate', + name: "InputStrandUpdate", definition(t) { - t.nonNull.string('driveId'); - t.nonNull.string('documentId'); - t.nonNull.string('scope'); - t.nonNull.string('branch'); - t.nonNull.list.nonNull.field('operations', { type: InputOperationUpdate }); + t.nonNull.string("driveId"); + t.nonNull.string("documentId"); + t.nonNull.string("scope"); + t.nonNull.string("branch"); + t.nonNull.list.nonNull.field("operations", { type: InputOperationUpdate }); }, }); export const UpdateStatus = enumType({ - name: 'UpdateStatus', - members: ['SUCCESS', 'MISSING', 'CONFLICT', 'ERROR'], + name: "UpdateStatus", + members: ["SUCCESS", "MISSING", "CONFLICT", "ERROR"], }); diff --git a/api/src/modules/DocumentDrive/model.ts b/api/src/modules/DocumentDrive/model.ts index f53d6b0e..0e9e7905 100644 --- a/api/src/modules/DocumentDrive/model.ts +++ b/api/src/modules/DocumentDrive/model.ts @@ -1,33 +1,29 @@ -import type { Prisma } from '@prisma/client'; +import type { Prisma } from "@prisma/client"; import { - CreateDocumentInput, DocumentDriveServer, DriveInput, - FilesystemStorage, + ListenerRevision, MemoryStorage, PrismaStorage, -} from 'document-drive'; -import * as DocumentModelsLibs from 'document-model-libs/document-models'; -import { module as DocumentModelLib } from 'document-model/document-model'; -import { - BaseAction, - DocumentModel, - Operation, -} from 'document-model/dist/browser/document'; -import { DocumentDriveAction } from 'document-model-libs/dist/document-models/document-drive'; +} from "document-drive"; +import * as DocumentModelsLibs from "document-model-libs/document-models"; +import { module as DocumentModelLib } from "document-model/document-model"; +import { DocumentModel, Operation } from "document-model/dist/browser/document"; +import { PullResponderTransmitter } from "document-drive/src/transmitter/pull-responder"; export function getDocumentDriveCRUD(prisma: Prisma.TransactionClient) { const documentModels = [ DocumentModelLib, ...Object.values(DocumentModelsLibs), ] as DocumentModel[]; + const driveServer = new DocumentDriveServer( documentModels, - new PrismaStorage(prisma), + new MemoryStorage() ); return { addDrive: async (args: DriveInput) => { - await driveServer.addDrive(args); + const drive = await driveServer.addDrive(args); return { ...args, }; @@ -64,9 +60,9 @@ export function getDocumentDriveCRUD(prisma: Prisma.TransactionClient) { return true; }, - addDocument: async (driveId: string, input: CreateDocumentInput) => { + addDocument: async (input: DriveInput) => { try { - await driveServer.createDocument(driveId, input); + await driveServer.addDrive(input); } catch (e) { console.log(e); return false; @@ -78,7 +74,7 @@ export function getDocumentDriveCRUD(prisma: Prisma.TransactionClient) { addOperation: async ( driveId: string, documentId: string, - operation: Operation, + operation: Operation ) => { return await driveServer.addOperation(driveId, documentId, operation); }, @@ -87,5 +83,66 @@ export function getDocumentDriveCRUD(prisma: Prisma.TransactionClient) { const result = await driveServer.addDriveOperations(driveId, operations); return result; }, + addOperations: async ( + driveId: string, + documentId: string, + operations: Operation[] + ) => { + const result = await driveServer.addOperations( + driveId, + documentId, + operations + ); + return result; + }, + + pushUpdates: async ( + driveId: string, + documentId: string, + operations: Operation[] + ) => { + const result = await driveServer.addOperations( + driveId, + documentId, + operations + ); + return result; + }, + + pullStrands: async ( + driveId: string, + listenerId: string, + since?: string + ) => { + const transmitter = (await driveServer.getTransmitter( + driveId, + listenerId + )) as PullResponderTransmitter; + if (!transmitter) { + throw new Error(`Transmitter with id ${listenerId} not found`); + } + + const result = await transmitter.getStrands(listenerId, since); + return result; + }, + + acknowledgeStrands: async ( + driveId: string, + listenerId: string, + revisions: ListenerRevision[] + ) => { + const transmitter = (await driveServer.getTransmitter( + driveId, + listenerId + )) as PullResponderTransmitter; + if (!transmitter) { + throw new Error(`Transmitter with id ${listenerId} not found`); + } + return await transmitter.acknowledgeStrands( + listenerId, + driveId, + revisions + ); + }, }; } diff --git a/api/src/modules/DocumentDrive/mutations/addDrive.ts b/api/src/modules/DocumentDrive/mutations/addDrive.ts index 0bf9e0d7..6ca8e411 100644 --- a/api/src/modules/DocumentDrive/mutations/addDrive.ts +++ b/api/src/modules/DocumentDrive/mutations/addDrive.ts @@ -1,25 +1,25 @@ -import { mutationField, nonNull, objectType } from 'nexus'; +import { mutationField, nonNull, objectType } from "nexus"; import { DocumentDriveLocalState, DocumentDriveLocalStateInput, DocumentDriveState, DocumentDriveStateInput, -} from '../definitions'; +} from "../definitions"; const addDriveResponseDefinition = objectType({ - name: 'AddDriveResponse', + name: "AddDriveResponse", definition(t) { - t.nonNull.field('global', { + t.nonNull.field("global", { type: DocumentDriveState, }); - t.nonNull.field('local', { + t.nonNull.field("local", { type: DocumentDriveLocalState, }); }, }); -export const addDrive = mutationField('addDrive', { +export const addDrive = mutationField("addDrive", { type: addDriveResponseDefinition, args: { global: nonNull(DocumentDriveStateInput), diff --git a/api/src/modules/DocumentDrive/mutations/addListener.ts b/api/src/modules/DocumentDrive/mutations/addListener.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/api/src/modules/DocumentDrive/mutations/pushUpdates.ts b/api/src/modules/DocumentDrive/mutations/pushUpdates.ts index 9e290eb6..be974cdc 100644 --- a/api/src/modules/DocumentDrive/mutations/pushUpdates.ts +++ b/api/src/modules/DocumentDrive/mutations/pushUpdates.ts @@ -1,8 +1,12 @@ -import { list, mutationField, nonNull } from 'nexus'; -import { InputStrandUpdate, ListenerRevision } from '../definitions'; -import { Operation, OperationScope } from 'document-model/document'; +import { list, mutationField, nonNull } from "nexus"; +import { InputStrandUpdate, ListenerRevision } from "../definitions"; +import { OperationScope } from "document-model/document"; +import { + ListenerRevision as IListenerRevision, + UpdateStatus, +} from "document-drive"; -export const pushUpdates = mutationField('pushUpdates', { +export const pushUpdates = mutationField("pushUpdates", { type: list(ListenerRevision), args: { strands: list(nonNull(InputStrandUpdate)), @@ -10,7 +14,8 @@ export const pushUpdates = mutationField('pushUpdates', { resolve: async (_parent, { strands }, ctx) => { //@todo: get connect drive server from ctx and apply updates - if (!strands || strands?.length === 0) return [[]]; + if (!strands || strands?.length === 0) return []; + const listenerRevisions: IListenerRevision[] = []; const results = await Promise.all( strands.map(async (s) => { const operations = s.operations?.map((o) => { @@ -24,28 +29,26 @@ export const pushUpdates = mutationField('pushUpdates', { return op; }); try { - const result = await ctx.prisma.document.addDriveOperations( + const result = await ctx.prisma.document.pushUpdates( s.driveId, - operations, + s.documentId, + operations ); - console.log(result); - return result; + + listenerRevisions.push({ + branch: s.branch, + documentId: s.documentId, + driveId: s.driveId, + revision: result.operations.pop().revision, + scope: s.scope as OperationScope, + status: (result.error ? "ERROR" : "SUCCESS") as UpdateStatus, + }); } catch (e) { console.log(e); - return null; } - }), + }) ); - return strands.map((r, i) => { - // if (!result) return null; - return { - driveId: r.driveId, - documentId: r.documentId, - scope: r.scope, - branch: r.branch, - status: 'SUCCESS', - revision: r.operations[r.operations.length - 1].index + 1, - }; - }); + + return listenerRevisions; }, }); diff --git a/api/src/modules/DocumentDrive/mutations/registerListener.ts b/api/src/modules/DocumentDrive/mutations/registerListener.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/api/src/modules/DocumentDrive/mutations/updateListener.ts b/api/src/modules/DocumentDrive/mutations/updateListener.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/api/src/modules/DocumentDrive/queries/pullUpdates.ts b/api/src/modules/DocumentDrive/queries/pullUpdates.ts index 7ff8fce9..7b629bc8 100644 --- a/api/src/modules/DocumentDrive/queries/pullUpdates.ts +++ b/api/src/modules/DocumentDrive/queries/pullUpdates.ts @@ -1,53 +1,103 @@ -import { idArg, list, nonNull, queryField, stringArg } from 'nexus'; -import { - ListenerRevision, - ListenerRevisionInput, - StrandUpdate, -} from '../definitions'; +import { idArg, list, mutationField, nonNull, queryField } from "nexus"; +import { ListenerRevisionInput, StrandUpdate } from "../definitions"; +import { ListenerRevision, ListenerStatus, UpdateStatus } from "document-drive"; -export const strands = queryField('strands', { +export const strands = queryField("strands", { type: list(StrandUpdate), args: { listenerId: idArg(), - revisions: list(ListenerRevisionInput), }, - resolve: async (_parent, args, ctx) => { + resolve: async (_parent, { listenerId }, ctx) => { try { - // @todo: fetch strands from connect drive server - return []; + const result = await ctx.prisma.document.pullStrands( + ctx.driveId ?? "1", + listenerId! + ); + return result.map((e) => { + return { + driveId: e.driveId, + documentId: e.documentId, + scope: e.scope, + branch: e.branch, + operations: e.operations.map((o) => ({ + revision: o.revision, + skip: o.skip, + name: o.operation, + inputJson: JSON.stringify(o.input), + stateHash: o.hash, + })), + }; + }); } catch (e) { + console.log(e); return []; } }, }); -export const strandsSince = queryField('strandsSince', { +export const strandsSince = queryField("strandsSince", { type: list(StrandUpdate), args: { listenerId: idArg(), - since: 'Date', + since: "Date", }, - resolve: async (_parent, args, ctx) => { + resolve: async (_parent, { listenerId, since }, ctx) => { try { // @todo: fetch strands from connect drive server - return []; + const result = await ctx.prisma.document.pullStrands( + ctx.driveId ?? "1", + listenerId!, + since + ); + return result.map((e) => { + return { + driveId: e.driveId, + documentId: e.documentId, + scope: e.scope, + branch: e.branch, + operations: e.operations.map((o) => ({ + revision: o.revision, + skip: o.skip, + name: o.operation, + inputJson: JSON.stringify(o.input), + stateHash: o.hash, + })), + }; + }); } catch (e) { + console.log(e); return []; } }, }); -export const acknowledge = queryField('acknowledge', { - type: 'Boolean', +export const acknowledge = mutationField("acknowledge", { + type: "Boolean", args: { - listenerId: idArg(), - revisions: list(ListenerRevisionInput), + listenerId: nonNull("String"), + revisions: nonNull(list(nonNull(ListenerRevisionInput))), }, - resolve: async (_parent, args, ctx) => { + resolve: async (_parent, { revisions, listenerId }, ctx) => { try { - // do something - return true; + if (!listenerId || !revisions) return false; + const validEntries: ListenerRevision[] = revisions + .filter((r) => r !== null) + .map((e) => ({ + driveId: ctx.driveId ?? "1", + documentId: e!.documentId, + scope: e!.scope, + branch: e!.branch, + revision: e!.revision, + status: e!.status as UpdateStatus, + })); + + return await ctx.prisma.document.acknowledgeStrands( + ctx.driveId ?? "1", + listenerId, + validEntries + ); } catch (e) { + console.log(e); return false; } },