From 0cc3428de048d59917f197ba623add96f4800d7a Mon Sep 17 00:00:00 2001 From: Kat Schelonka Date: Tue, 28 Jan 2025 09:39:55 -0800 Subject: [PATCH 1/2] chore: remove noteFromQuote mutations Clients will handle formatting (or not) quoted notes using the createNote mutations instead. --- servers/notes-api/schema.graphql | 76 ------- servers/notes-api/src/apollo/resolvers.ts | 6 - servers/notes-api/src/models/Note.ts | 46 +--- .../src/models/ProseMirrorDoc.spec.ts | 40 +--- .../notes-api/src/models/ProseMirrorDoc.ts | 74 ------- .../src/test/documents/fromQuote.json | 204 ------------------ .../src/test/documents/fromQuoteMd.txt | 11 - .../createNoteFromQuote.integration.ts | 157 -------------- ...createNoteFromQuoteMarkdown.integration.ts | 122 ----------- 9 files changed, 2 insertions(+), 734 deletions(-) delete mode 100644 servers/notes-api/src/test/documents/fromQuote.json delete mode 100644 servers/notes-api/src/test/documents/fromQuoteMd.txt delete mode 100644 servers/notes-api/src/test/mutations/createNoteFromQuote.integration.ts delete mode 100644 servers/notes-api/src/test/mutations/createNoteFromQuoteMarkdown.integration.ts diff --git a/servers/notes-api/schema.graphql b/servers/notes-api/schema.graphql index 829b38633..e3e6af582 100644 --- a/servers/notes-api/schema.graphql +++ b/servers/notes-api/schema.graphql @@ -277,72 +277,6 @@ input CreateNoteMarkdownInput { createdAt: ISOString } -""" -Input to create a new Note seeded with copied content from a page. -The entire content becomes editable and is not able to be "reattached" -like a traditional highlight. -""" -input CreateNoteFromQuoteInput { - """Optional title for this Note""" - title: String - """ - Client-provided UUID for the new Note. - If not provided, will be generated on the server. - """ - id: ID - """ - The Web Resource where the quote is taken from. - This should always be sent by the client where possible, - but in some cases (e.g. copying from mobile apps) there may - not be an accessible source url. - """ - source: ValidUrl - """ - JSON representation of a ProseMirror document, which - contains the formatted snipped text. This is used to seed - the initial Note document state, and will become editable. - """ - quote: ProseMirrorJson! - """ - When this note was created. If not provided, defaults to server time upon - receiving request. - """ - createdAt: ISOString -} - -""" -Input to create a new Note seeded with copied content from a page. -The entire content becomes editable and is not able to be "reattached" -like a traditional highlight. -""" -input CreateNoteFromQuoteMarkdownInput { - """Optional title for this Note""" - title: String - """ - Client-provided UUID for the new Note. - If not provided, will be generated on the server. - """ - id: ID - """ - The Web Resource where the quote is taken from. - This should always be sent by the client where possible, - but in some cases (e.g. copying from mobile apps) there may - not be an accessible source url. - """ - source: ValidUrl - """ - Commonmark Markdown document, which contains the formatted - snipped text. This is used to seed the initial Note - document state, and will become editable. - """ - quote: Markdown! - """ - When this note was created. If not provided, defaults to server time upon - receiving request. - """ - createdAt: ISOString -} - input EditNoteTitleInput { """The ID of the note to edit""" id: ID! @@ -427,16 +361,6 @@ type Mutation { Create a new note, optionally with title and markdown content """ createNoteMarkdown(input: CreateNoteMarkdownInput!): Note! @requiresScopes(scopes: [["ROLE_USER"]]) - """ - Create a new note, with a pre-populated block that contains the quoted and cited text - selected by a user. - """ - createNoteFromQuote(input: CreateNoteFromQuoteInput!): Note! @requiresScopes(scopes: [["ROLE_USER"]]) - """ - Create a new note, with a pre-populated block that contains the quoted and cited text - selected by a user. - """ - createNoteFromQuoteMarkdown(input: CreateNoteFromQuoteMarkdownInput!): Note! @requiresScopes(scopes: [["ROLE_USER"]]) """ Edit the title of a Note. diff --git a/servers/notes-api/src/apollo/resolvers.ts b/servers/notes-api/src/apollo/resolvers.ts index 8ec7ef914..160992de5 100644 --- a/servers/notes-api/src/apollo/resolvers.ts +++ b/servers/notes-api/src/apollo/resolvers.ts @@ -69,12 +69,6 @@ export const resolvers: Resolvers = { createNoteMarkdown(root, { input }, context) { return context.NoteModel.createFromMarkdown(input); }, - createNoteFromQuote(root, { input }, context) { - return context.NoteModel.fromQuote(input); - }, - createNoteFromQuoteMarkdown(root, { input }, context) { - return context.NoteModel.fromMarkdownQuote(input); - }, editNoteTitle(root, { input }, context) { return context.NoteModel.editTitle(input); }, diff --git a/servers/notes-api/src/models/Note.ts b/servers/notes-api/src/models/Note.ts index 679234776..3ef939a33 100644 --- a/servers/notes-api/src/models/Note.ts +++ b/servers/notes-api/src/models/Note.ts @@ -16,11 +16,7 @@ import { Insertable, NoResultError, Selectable } from 'kysely'; import { orderAndMap } from '../utils/dataloader'; import { IContext } from '../apollo/context'; import { NotesService } from '../datasources/NoteService'; -import { - docFromMarkdown, - ProseMirrorDoc, - wrapDocInBlockQuote, -} from './ProseMirrorDoc'; +import { docFromMarkdown, ProseMirrorDoc } from './ProseMirrorDoc'; import { DatabaseError } from 'pg'; import { NoteFilterInput, @@ -192,46 +188,6 @@ export class NoteModel { }; return await this.create(createInput); } - /** - * Create a new Note seeded with a blockquote (optionally with - * an additional paragraph with the source link). - */ - async fromQuote(input: CreateNoteFromQuoteInput) { - try { - const docContent = JSON.parse(input.quote); - const options = - input.source != null ? { source: input.source.toString() } : undefined; - const quotedDoc = wrapDocInBlockQuote(docContent, options); - const createInput: CreateNoteInput = { - docContent: JSON.stringify(quotedDoc), - createdAt: input.createdAt, - id: input.id, - title: input.title, - source: input.source, - }; - return this.create(createInput); - } catch (error) { - if (error instanceof SyntaxError) { - throw new UserInputError( - `Received malformed JSON for docContent: ${error.message}`, - ); - } else { - throw error; - } - } - } - /** - * Create a new Note seeded with a blockquote (optionally with - * an additional paragraph with the source link). - */ - async fromMarkdownQuote(input: CreateNoteFromQuoteMarkdownInput) { - const quote = docFromMarkdown(input.quote); - const createInput: CreateNoteFromQuoteInput = { - ...input, - quote: JSON.stringify(quote.toJSON()), - }; - return await this.fromQuote(createInput); - } /** * Edit a note's title */ diff --git a/servers/notes-api/src/models/ProseMirrorDoc.spec.ts b/servers/notes-api/src/models/ProseMirrorDoc.spec.ts index 3f56bd21a..e1d06469a 100644 --- a/servers/notes-api/src/models/ProseMirrorDoc.spec.ts +++ b/servers/notes-api/src/models/ProseMirrorDoc.spec.ts @@ -1,13 +1,7 @@ import basicText from '../test/documents/basicText.json'; -import { - docFromMarkdown, - ProseMirrorDoc, - wrapDocInBlockQuote, -} from './ProseMirrorDoc'; +import { docFromMarkdown, ProseMirrorDoc } from './ProseMirrorDoc'; import { schema } from 'prosemirror-markdown'; -import fromQuote from '../test/documents/fromQuote.json'; import badMd from '../test/documents/badMd.json'; -import { UserInputError } from '@pocket-tools/apollo-utils'; import * as fs from 'fs'; import path from 'path'; @@ -30,38 +24,6 @@ describe('ProseMirrorDoc', () => { expect(doc.markdown).toBeString(); }); }); - describe('quote constructor', () => { - it('wraps quote in blockquote and adds attribution', () => { - const { input, expectedSource } = fromQuote; - const actual = wrapDocInBlockQuote(input, { source: 'localhost:3001' }); - expect(actual).toEqual(expectedSource); - }); - it('wraps quote in blockquote without attribution', () => { - const { input, expectedNoSource } = fromQuote; - const actual = wrapDocInBlockQuote(input); - expect(actual).toEqual(expectedNoSource); - }); - it('throws error if an invalid node is encountered', () => { - const bad = { - type: 'doc', - content: [ - { - type: 'invalid', - content: [ - { - type: 'text', - text: '', - }, - ], - }, - ], - }; - expect(() => wrapDocInBlockQuote(bad)).toThrowWithMessage( - UserInputError, - /.*Invalid Document.*/, - ); - }); - }); describe('markdown parser', () => { it('parses plain text paragraphs', () => { const doc = docFromMarkdown(basicTextMd); diff --git a/servers/notes-api/src/models/ProseMirrorDoc.ts b/servers/notes-api/src/models/ProseMirrorDoc.ts index 48d6948bb..e31ea8204 100644 --- a/servers/notes-api/src/models/ProseMirrorDoc.ts +++ b/servers/notes-api/src/models/ProseMirrorDoc.ts @@ -47,77 +47,3 @@ export class ProseMirrorDoc { export function docFromMarkdown(doc: string) { return defaultMarkdownParser.parse(doc); } - -/** - * Wrap a JSON representation of a ProseMirror document in a blockquote, - * optionally with an additional paragraph that references the source - * link from where the document was copied. - * Returns a JSON-serializable representation of the new formatted document. - */ -export function wrapDocInBlockQuote< - S extends Schema<'blockquote' | 'paragraph' | 'text', 'link'>, ->(quoteDoc: any, options?: { source?: string; schema?: S }): any { - const opts = options ?? {}; - Sentry.addBreadcrumb({ - message: 'wrapDocInBlockQuote: input document', - type: 'log', - timestamp: Date.now(), - data: { source: opts.source, doc: quoteDoc }, - }); - const schema = opts.schema ?? commonMarkSchema; - const source = opts.source; - let initialState: EditorState; - try { - initialState = EditorState.create({ doc: Node.fromJSON(schema, quoteDoc) }); - } catch (error) { - if (error instanceof RangeError) { - serverLogger.warn({ - message: 'Attempted to parse document with unknown node type', - errorData: error, - document: quoteDoc, - }); - throw new UserInputError(`Invalid Document: ${error.message}`); - } else { - throw error; - } - } - // Kind of a silly closure, but helps keep this more organized - // without having to pass along everything to a new function for - // error reporting/logging - const wrapDoc = (state: EditorState) => { - // Logic for wrapping in blockquote - const docSelect = new AllSelection(state.doc); - const range = docSelect.$from.blockRange(docSelect.$to); - if (range == null) { - const message = `Could not generate range from document`; - serverLogger.error({ message, document: quoteDoc, source }); - throw new UserInputError(`${message} -- is the document malformed?`); - } else { - const wrapping = findWrapping(range, schema.nodes.blockquote, {}); - if (wrapping == null) { - const message = `Could not wrap document selection`; - serverLogger.error({ message, document: quoteDoc, source }); - throw new UserInputError(`${message} -- is the document malformed?`); - } else { - const transaction = state.tr.wrap(range, wrapping); - const trxResult = state.applyTransaction(transaction); - return trxResult.state; - } - } - }; - // Wrap document in blockquote - const state = wrapDoc(initialState); - // Insert paragraph with source link if provided - if (source != null) { - const node = schema.node('paragraph', {}, [ - schema.text('Source: '), - schema.text(source, [schema.mark('link', { href: source })]), - ]); - const transaction = state.tr.insert(state.tr.doc.content.size, node); - const trxResult = state.applyTransaction(transaction); - return trxResult.state.doc.toJSON(); - } else { - // Just return the blockquoted-doc - return state.doc.toJSON(); - } -} diff --git a/servers/notes-api/src/test/documents/fromQuote.json b/servers/notes-api/src/test/documents/fromQuote.json deleted file mode 100644 index ee70f8ab5..000000000 --- a/servers/notes-api/src/test/documents/fromQuote.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "input": { - "type": "doc", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "This is something I’m copying from somewhere else" - } - ] - }, - { - "type": "paragraph", - "content": [ - { "type": "text", "text": "I want to make everything a blockquote" } - ] - }, - { - "type": "paragraph", - "content": [{ "type": "text", "text": "Here’s a list:" }] - }, - { - "type": "bullet_list", - "content": [ - { - "type": "list_item", - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "abc" }] - } - ] - }, - { - "type": "list_item", - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "123" }] - } - ] - }, - { - "type": "list_item", - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "def" }] - } - ] - } - ] - } - ] - }, - "expectedSource": { - "type": "doc", - "content": [ - { - "type": "blockquote", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "This is something I’m copying from somewhere else" - } - ] - }, - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "I want to make everything a blockquote" - } - ] - }, - { - "type": "paragraph", - "content": [{ "type": "text", "text": "Here’s a list:" }] - }, - { - "type": "bullet_list", - "attrs": { "tight": false }, - "content": [ - { - "type": "list_item", - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "abc" }] - } - ] - }, - { - "type": "list_item", - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "123" }] - } - ] - }, - { - "type": "list_item", - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "def" }] - } - ] - } - ] - } - ] - }, - { - "type": "paragraph", - "content": [ - { "type": "text", "text": "Source: " }, - { - "type": "text", - "marks": [ - { - "type": "link", - "attrs": { "href": "localhost:3001", "title": null } - } - ], - "text": "localhost:3001" - } - ] - } - ] - }, - "expectedNoSource": { - "type": "doc", - "content": [ - { - "type": "blockquote", - "content": [ - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "This is something I’m copying from somewhere else" - } - ] - }, - { - "type": "paragraph", - "content": [ - { - "type": "text", - "text": "I want to make everything a blockquote" - } - ] - }, - { - "type": "paragraph", - "content": [{ "type": "text", "text": "Here’s a list:" }] - }, - { - "type": "bullet_list", - "attrs": { "tight": false }, - "content": [ - { - "type": "list_item", - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "abc" }] - } - ] - }, - { - "type": "list_item", - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "123" }] - } - ] - }, - { - "type": "list_item", - "content": [ - { - "type": "paragraph", - "content": [{ "type": "text", "text": "def" }] - } - ] - } - ] - } - ] - } - ] - } -} diff --git a/servers/notes-api/src/test/documents/fromQuoteMd.txt b/servers/notes-api/src/test/documents/fromQuoteMd.txt deleted file mode 100644 index bc77c2be9..000000000 --- a/servers/notes-api/src/test/documents/fromQuoteMd.txt +++ /dev/null @@ -1,11 +0,0 @@ -This is something I’m copying from somewhere else - -I want to make everything a blockquote - -Here’s a list: - -* abc - -* 123 - -* def \ No newline at end of file diff --git a/servers/notes-api/src/test/mutations/createNoteFromQuote.integration.ts b/servers/notes-api/src/test/mutations/createNoteFromQuote.integration.ts deleted file mode 100644 index 62a4defc7..000000000 --- a/servers/notes-api/src/test/mutations/createNoteFromQuote.integration.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { type ApolloServer } from '@apollo/server'; -import request from 'supertest'; -import { IContext, startServer } from '../../apollo'; -import { type Application } from 'express'; -import { CREATE_NOTE_QUOTE } from '../operations'; -import { db } from '../../datasources/db'; -import { sql } from 'kysely'; -import { CreateNoteFromQuoteInput } from '../../__generated__/graphql'; -import fromQuote from '../documents/fromQuote.json'; -import { Chance } from 'chance'; - -let app: Application; -let server: ApolloServer; -let graphQLUrl: string; - -beforeAll(async () => { - // port 0 tells express to dynamically assign an available port - ({ app, server, url: graphQLUrl } = await startServer(0)); -}); -afterAll(async () => { - await sql`truncate table ${sql.table('Note')} CASCADE`.execute(db); - await server.stop(); - await db.destroy(); -}); - -describe('note', () => { - it('creates a note with minimal inputs', async () => { - const input: CreateNoteFromQuoteInput = { - quote: JSON.stringify(fromQuote.input), - }; - const res = await request(app) - .post(graphQLUrl) - .set({ userid: '1' }) - .send({ query: CREATE_NOTE_QUOTE, variables: { input } }); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data?.createNoteFromQuote).toMatchObject({ - archived: false, - contentPreview: expect.toBeString(), - createdAt: expect.toBeDateString(), - deleted: false, - id: expect.toBeString(), - savedItem: null, - source: null, - title: null, - updatedAt: expect.toBeDateString(), - }); - // The keys may get reordered so we have to deeply compare the - // JSON-serialized results - const receivedDoc = res.body.data?.createNoteFromQuote?.docContent; - expect(receivedDoc).not.toBeNil(); - expect(JSON.parse(receivedDoc)).toStrictEqual(fromQuote.expectedNoSource); - }); - it('creates a note with optional fields', async () => { - const chance = new Chance(); - const createdAt = new Date(chance.hammertime()); - const input: CreateNoteFromQuoteInput = { - title: chance.sentence(), - createdAt, - source: 'localhost:3001', - id: chance.guid({ version: 4 }), - quote: JSON.stringify(fromQuote.input), - }; - const res = await request(app) - .post(graphQLUrl) - .set({ userid: '1' }) - .send({ query: CREATE_NOTE_QUOTE, variables: { input } }); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data?.createNoteFromQuote).toMatchObject({ - archived: false, - contentPreview: expect.toBeString(), - createdAt: new Date( - Math.round(createdAt.getTime() / 1000) * 1000, - ).toISOString(), - deleted: false, - id: input.id, - savedItem: { - url: input.source, - }, - source: input.source, - title: input.title, - updatedAt: createdAt.toISOString(), - }); - // The keys may get reordered so we have to deeply compare the - // JSON-serialized results - const receivedDoc = res.body.data?.createNoteFromQuote?.docContent; - expect(receivedDoc).not.toBeNil(); - expect(JSON.parse(receivedDoc)).toStrictEqual(fromQuote.expectedSource); - }); - it('throws error for duplicate UUID', async () => { - const uuid = 'ccab26fb-64a5-4071-9044-f42bc2470884'; - const input: CreateNoteFromQuoteInput = { - quote: JSON.stringify(fromQuote.input), - }; - const seed = await request(app) - .post(graphQLUrl) - .set({ userid: '1' }) - .send({ - query: CREATE_NOTE_QUOTE, - variables: { input: { ...input, id: uuid } }, - }); - expect(seed.body.errors).toBeNil(); - const res = await request(app) - .post(graphQLUrl) - .set({ userid: '1' }) - .send({ - query: CREATE_NOTE_QUOTE, - variables: { input: { ...input, id: uuid } }, - }); - expect(res.body.errors).toBeArrayOfSize(1); - expect(res.body.errors[0].extensions.code).toEqual('BAD_USER_INPUT'); - expect(res.body.errors[0].message).toMatch( - 'Received duplicate value for note ID', - ); - }); - it('throws error for bad JSON', async () => { - const input: CreateNoteFromQuoteInput = { - quote: "{ 'bad': 'json'", - }; - const res = await request(app) - .post(graphQLUrl) - .set({ userid: '1' }) - .send({ query: CREATE_NOTE_QUOTE, variables: { input } }); - - expect(res.body.errors).toBeArrayOfSize(1); - expect(res.body.errors[0].extensions.code).toEqual('BAD_USER_INPUT'); - expect(res.body.errors[0].message).toMatch( - 'Received malformed JSON for docContent', - ); - }); - it('throws error for unparseable quote document', async () => { - const bad = { - type: 'doc', - content: [ - { - type: 'invalid', - content: [ - { - type: 'text', - text: '', - }, - ], - }, - ], - }; - const input: CreateNoteFromQuoteInput = { - quote: JSON.stringify(bad), - }; - const res = await request(app) - .post(graphQLUrl) - .set({ userid: '1' }) - .send({ query: CREATE_NOTE_QUOTE, variables: { input } }); - - expect(res.body.errors).toBeArrayOfSize(1); - expect(res.body.errors[0].extensions.code).toEqual('BAD_USER_INPUT'); - expect(res.body.errors[0].message).toMatch('Invalid Document'); - }); -}); diff --git a/servers/notes-api/src/test/mutations/createNoteFromQuoteMarkdown.integration.ts b/servers/notes-api/src/test/mutations/createNoteFromQuoteMarkdown.integration.ts deleted file mode 100644 index 7319f1486..000000000 --- a/servers/notes-api/src/test/mutations/createNoteFromQuoteMarkdown.integration.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { type ApolloServer } from '@apollo/server'; -import request from 'supertest'; -import { IContext, startServer } from '../../apollo'; -import { type Application } from 'express'; -import { CREATE_NOTE_QUOTE_MD } from '../operations'; -import { db } from '../../datasources/db'; -import { sql } from 'kysely'; -import { CreateNoteFromQuoteMarkdownInput } from '../../__generated__/graphql'; -import fromQuote from '../documents/fromQuote.json'; -import { Chance } from 'chance'; -import * as fs from 'fs'; -import path from 'path'; - -const fromQuoteMd = fs.readFileSync( - path.resolve(__dirname, '../documents/fromQuoteMd.txt'), - 'utf8', -); - -let app: Application; -let server: ApolloServer; -let graphQLUrl: string; - -beforeAll(async () => { - // port 0 tells express to dynamically assign an available port - ({ app, server, url: graphQLUrl } = await startServer(0)); -}); -afterAll(async () => { - await sql`truncate table ${sql.table('Note')} CASCADE`.execute(db); - await server.stop(); - await db.destroy(); -}); - -describe('note', () => { - it('creates a note with minimal inputs', async () => { - const input: CreateNoteFromQuoteMarkdownInput = { - quote: fromQuoteMd, - }; - const res = await request(app) - .post(graphQLUrl) - .set({ userid: '1' }) - .send({ query: CREATE_NOTE_QUOTE_MD, variables: { input } }); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data?.createNoteFromQuoteMarkdown).toMatchObject({ - archived: false, - contentPreview: expect.toBeString(), - createdAt: expect.toBeDateString(), - deleted: false, - id: expect.toBeString(), - savedItem: null, - source: null, - title: null, - updatedAt: expect.toBeDateString(), - }); - // The keys may get reordered so we have to deeply compare the - // JSON-serialized results - const receivedDoc = res.body.data?.createNoteFromQuoteMarkdown?.docContent; - expect(receivedDoc).not.toBeNil(); - expect(JSON.parse(receivedDoc)).toStrictEqual(fromQuote.expectedNoSource); - }); - it('creates a note with optional fields', async () => { - const chance = new Chance(); - const createdAt = new Date(chance.hammertime()); - const input: CreateNoteFromQuoteMarkdownInput = { - title: chance.sentence(), - createdAt, - source: 'localhost:3001', - id: chance.guid({ version: 4 }), - quote: fromQuoteMd, - }; - const res = await request(app) - .post(graphQLUrl) - .set({ userid: '1' }) - .send({ query: CREATE_NOTE_QUOTE_MD, variables: { input } }); - expect(res.body.errors).toBeUndefined(); - expect(res.body.data?.createNoteFromQuoteMarkdown).toMatchObject({ - archived: false, - contentPreview: expect.toBeString(), - createdAt: new Date( - Math.round(createdAt.getTime() / 1000) * 1000, - ).toISOString(), - deleted: false, - id: input.id, - savedItem: { - url: input.source, - }, - source: input.source, - title: input.title, - updatedAt: createdAt.toISOString(), - }); - // The keys may get reordered so we have to deeply compare the - // JSON-serialized results - const receivedDoc = res.body.data?.createNoteFromQuoteMarkdown?.docContent; - expect(receivedDoc).not.toBeNil(); - expect(JSON.parse(receivedDoc)).toStrictEqual(fromQuote.expectedSource); - }); - it('throws error for duplicate UUID', async () => { - const uuid = 'ccab26fb-64a5-4071-9044-f42bc2470884'; - const input: CreateNoteFromQuoteMarkdownInput = { - quote: JSON.stringify(fromQuote.input), - }; - const seed = await request(app) - .post(graphQLUrl) - .set({ userid: '1' }) - .send({ - query: CREATE_NOTE_QUOTE_MD, - variables: { input: { ...input, id: uuid } }, - }); - expect(seed.body.errors).toBeNil(); - const res = await request(app) - .post(graphQLUrl) - .set({ userid: '1' }) - .send({ - query: CREATE_NOTE_QUOTE_MD, - variables: { input: { ...input, id: uuid } }, - }); - expect(res.body.errors).toBeArrayOfSize(1); - expect(res.body.errors[0].extensions.code).toEqual('BAD_USER_INPUT'); - expect(res.body.errors[0].message).toMatch( - 'Received duplicate value for note ID', - ); - }); -}); From 760cae0ebb730b1452e486d458037b1e8cfdd0be Mon Sep 17 00:00:00 2001 From: Kat Schelonka Date: Tue, 28 Jan 2025 09:46:53 -0800 Subject: [PATCH 2/2] chore: lint and codegen --- .../notes-api/src/__generated__/graphql.ts | 96 +------------------ servers/notes-api/src/models/Note.ts | 2 - .../notes-api/src/models/ProseMirrorDoc.ts | 5 - 3 files changed, 2 insertions(+), 101 deletions(-) diff --git a/servers/notes-api/src/__generated__/graphql.ts b/servers/notes-api/src/__generated__/graphql.ts index f0d01c373..61cdf826e 100644 --- a/servers/notes-api/src/__generated__/graphql.ts +++ b/servers/notes-api/src/__generated__/graphql.ts @@ -42,72 +42,6 @@ export type ArchiveNoteInput = { updatedAt?: InputMaybe; }; -/** - * Input to create a new Note seeded with copied content from a page. - * The entire content becomes editable and is not able to be "reattached" - * like a traditional highlight. - */ -export type CreateNoteFromQuoteInput = { - /** - * When this note was created. If not provided, defaults to server time upon - * receiving request. - */ - createdAt?: InputMaybe; - /** - * Client-provided UUID for the new Note. - * If not provided, will be generated on the server. - */ - id?: InputMaybe; - /** - * JSON representation of a ProseMirror document, which - * contains the formatted snipped text. This is used to seed - * the initial Note document state, and will become editable. - */ - quote: Scalars['ProseMirrorJson']['input']; - /** - * The Web Resource where the quote is taken from. - * This should always be sent by the client where possible, - * but in some cases (e.g. copying from mobile apps) there may - * not be an accessible source url. - */ - source?: InputMaybe; - /** Optional title for this Note */ - title?: InputMaybe; -}; - -/** - * Input to create a new Note seeded with copied content from a page. - * The entire content becomes editable and is not able to be "reattached" - * like a traditional highlight. - */ -export type CreateNoteFromQuoteMarkdownInput = { - /** - * When this note was created. If not provided, defaults to server time upon - * receiving request. - */ - createdAt?: InputMaybe; - /** - * Client-provided UUID for the new Note. - * If not provided, will be generated on the server. - */ - id?: InputMaybe; - /** - * Commonmark Markdown document, which contains the formatted - * snipped text. This is used to seed the initial Note - * document state, and will become editable. - */ - quote: Scalars['Markdown']['input']; - /** - * The Web Resource where the quote is taken from. - * This should always be sent by the client where possible, - * but in some cases (e.g. copying from mobile apps) there may - * not be an accessible source url. - */ - source?: InputMaybe; - /** Optional title for this Note */ - title?: InputMaybe; -}; - /** Input to create a new Note */ export type CreateNoteInput = { /** @@ -210,16 +144,6 @@ export type Mutation = { archiveNote?: Maybe; /** Create a new note, optionally with title and content */ createNote: Note; - /** - * Create a new note, with a pre-populated block that contains the quoted and cited text - * selected by a user. - */ - createNoteFromQuote: Note; - /** - * Create a new note, with a pre-populated block that contains the quoted and cited text - * selected by a user. - */ - createNoteFromQuoteMarkdown: Note; /** Create a new note, optionally with title and markdown content */ createNoteMarkdown: Note; /** @@ -270,16 +194,6 @@ export type MutationCreateNoteArgs = { }; -export type MutationCreateNoteFromQuoteArgs = { - input: CreateNoteFromQuoteInput; -}; - - -export type MutationCreateNoteFromQuoteMarkdownArgs = { - input: CreateNoteFromQuoteMarkdownInput; -}; - - export type MutationCreateNoteMarkdownArgs = { input: CreateNoteMarkdownInput; }; @@ -599,10 +513,8 @@ export type DirectiveResolverFn; - CreateNoteFromQuoteInput: CreateNoteFromQuoteInput; - String: ResolverTypeWrapper; - CreateNoteFromQuoteMarkdownInput: CreateNoteFromQuoteMarkdownInput; CreateNoteInput: CreateNoteInput; + String: ResolverTypeWrapper; CreateNoteMarkdownInput: CreateNoteMarkdownInput; DeleteNoteInput: DeleteNoteInput; EditNoteContentInput: EditNoteContentInput; @@ -633,10 +545,8 @@ export type ResolversTypes = ResolversObject<{ export type ResolversParentTypes = ResolversObject<{ ArchiveNoteInput: ArchiveNoteInput; ID: Scalars['ID']['output']; - CreateNoteFromQuoteInput: CreateNoteFromQuoteInput; - String: Scalars['String']['output']; - CreateNoteFromQuoteMarkdownInput: CreateNoteFromQuoteMarkdownInput; CreateNoteInput: CreateNoteInput; + String: Scalars['String']['output']; CreateNoteMarkdownInput: CreateNoteMarkdownInput; DeleteNoteInput: DeleteNoteInput; EditNoteContentInput: EditNoteContentInput; @@ -672,8 +582,6 @@ export interface MarkdownScalarConfig extends GraphQLScalarTypeConfig = ResolversObject<{ archiveNote?: Resolver, ParentType, ContextType, RequireFields>; createNote?: Resolver>; - createNoteFromQuote?: Resolver>; - createNoteFromQuoteMarkdown?: Resolver>; createNoteMarkdown?: Resolver>; deleteNote?: Resolver>; editNoteContent?: Resolver, ParentType, ContextType, RequireFields>; diff --git a/servers/notes-api/src/models/Note.ts b/servers/notes-api/src/models/Note.ts index 3ef939a33..84311c32d 100644 --- a/servers/notes-api/src/models/Note.ts +++ b/servers/notes-api/src/models/Note.ts @@ -2,11 +2,9 @@ import DataLoader from 'dataloader'; import { Note, CreateNoteInput, - CreateNoteFromQuoteInput, EditNoteTitleInput, EditNoteContentInput, DeleteNoteInput, - CreateNoteFromQuoteMarkdownInput, CreateNoteMarkdownInput, EditNoteContentMarkdownInput, ArchiveNoteInput, diff --git a/servers/notes-api/src/models/ProseMirrorDoc.ts b/servers/notes-api/src/models/ProseMirrorDoc.ts index e31ea8204..ed1eba919 100644 --- a/servers/notes-api/src/models/ProseMirrorDoc.ts +++ b/servers/notes-api/src/models/ProseMirrorDoc.ts @@ -1,14 +1,9 @@ import { Node, Schema } from 'prosemirror-model'; -import { EditorState, AllSelection } from 'prosemirror-state'; -import { findWrapping } from 'prosemirror-transform'; import { defaultMarkdownSerializer, defaultMarkdownParser, schema as commonMarkSchema, } from 'prosemirror-markdown'; -import { UserInputError } from '@pocket-tools/apollo-utils'; -import { serverLogger } from '@pocket-tools/ts-logger'; -import * as Sentry from '@sentry/node'; /** * Class for handling ProseMirror documents