You can submit all the answers to this assignment in a single repository (or as a zipped folder), containing markdown and code.
For questions 3-5 use the latest Node LTS.
Tell us about one of your commercial projects with Node.js and/or AngularJS.
Detail how you would store several versioned, text-based documents, and present a schema for your solution.
It should be able to show:
- the document in its current state
- the document at any point in its history
- the changes made between two versions
Strive for disk space efficiency.
Implement a REST API using Express.js that handles Export and Import requests. The solution should ideally be written in Typescript, or else using plain JavaScript's
class
structure.The API should expose endpoints to:
POST
a request for a new Export job. Valid requests should be saved in memory. Invalid requests should return an error. The request must have the following schema:{ bookId: string, type: "epub" | "pdf" }
GET
a list of Export requests, grouped by their currentstate
(see below).POST
a request for a new Import job. Valid requests should be saved in memory. Invalid requests should return an error. The request must have the following schema:{ bookId: string, type: "word" | "pdf" | "wattpad" | "evernote", url: string }
GET
a list of Import requests, grouped by their currentstate
(see below).Both export and import requests should be created with a
pending
state, and with acreated_at
timestamp. An import or export should take the amount of time outlined below. After the specified time, the state should be updated frompending
tofinished
and update anupdated_at
timestamp.
Job type Processing time (s) ePub export 10 PDF export 25 import (any) 60 Add test coverage as you see fit.
The project should be responsible for managing all the required dependencies and should run just by using:
yarn install
+yarn start
ornpm install
+npm start
.
See Book Requests Porter.
Using Vue.js or AngularJS (1.x), create a basic SPA that implements the following UI:
- Each page should display 5 books;
- A few pages should be available in order for pagination to work;
- Book entries should be clickable and expand/collapse to show/hide more > information about the selected book;
- Book store links should only be displayed when the respective URL is available; make different entries as represented on the images above so different store availability scenarios are represented;
- Improve the UI as you think works best.
Add test coverage as you see fit.
The project should be responsible for managing all the required dependencies and should run just by using:
yarn install
+yarn start
ornpm install
+npm start
.
See Books SPA.
NB: You can duplicate the books in /src/store/books.json
to see the paging working with many pages.
When multiple users are collaborating on a document, collisions in their edits inevitably occur. Implement a module that can handle basic text update operations, and combine two colliding edits into a single operation.
An operation is described as an array of any combination of three types of edits:
{ move: number }
to advance the caret{ insert: string }
to insert the string at caret{ delete: number }
to delete a number of chars from the caret onwardsImplement the following methods:
Operation.prototype.combine(operation)
Updates the operation by combining it with another colliding operationOperation.combine(op1, op2)
Static method that returns a new operation by combining the arguments without mutating themOperation.prototype.apply(string)
Applies the operation to the provided argumentFor example:
const s = "abcdefg"; const op1 = new Operation([{ move: 1 }, { insert: "FOO" }]); const op2 = new Operation([{ move: 3 }, { insert: "BAR" }]); op1.apply(s); // => "aFOObcdefg" op2.apply(s); // => "abcBARdefg" const combined1 = Operation.combine(op1, op2); // => [{ move: 1 }, { insert: 'FOO' }, { move: 2}, { insert: 'BAR' } ] combined1.apply(s); // => "aFOObcBARdefg" const combined2 = Operation.combine(op2, op1); // NB: This expectation is true for this specific case, but not in the general case. // Can you think of an example where this assertion might not be true? expect(combined2.apply(s)).to.equal(combined1.apply(s));Add test coverage to demonstrate the module functionality. Again, TypeScript is preferred in this solution.
The project should be responsible for managing all the required dependencies and should run just by using:
yarn install
+yarn test
ornpm install
+npm test
.
-
I have given an object oriented-solution but, while working on it, I have wondered if a functional solution would be more performant.
-
I have assumed that inserting text also moves the caret. For example, inserting
'abc'
at position 6 results in the caret being at position 9 (after the inserted text). It could have been intended that the position should remain 6 and so the caret is still at the start of the inserted text. However, I think that the option I have chosen results in a cleaner implementation. This is because it prioritises modification to the original text rather than to the modification that was just made. This seems sensible to me. -
I have also assumed that it is OK to combine and reorder operations where the application results in the same text. I do recognise that this loses track of the history of the edits (if each individual edit needed to be displayed to a user). For example:
[{ move: 2 }, { move: 4 }] === [{ move: 6 }];
[{ delete: 2 }, { delete: 3 }] === [{ delete: 5 }];
[{ insert: 'abc' }, { delete: 3 }] === [{ delete: 3 }, { insert: 'abc' }];
[{ insert: 'abc' }, { insert: 'def' }] === [{ insert: 'abcdef' }];
[{ insert: 'abc' }, { delete: 3 }, { insert: 'def' }] === [{ delete: 3 }, { insert: 'abcdef' }];
It strikes me that an ideal solution to this problem would always return the same result, regardless of which operation is applied is the first operation. If I think of handling conflicts in GIT and what an automated merge strategy could look like, I don't think that a tool would be useful if the following were true: merging branch B into branch A results in branch A being different to what branch B would be if we merge branch A into it.
So, I think the ideal solution would follow GITs example and present these conflicts to a user, who can manually resolve them.
However, if we are strictly combining the results and cannot ask for human intervention there are several tricky situations:
- If one operation tries to insert into a location that the other has deleted. It strikes me that the most sensible way to resolve this issue is to prioritise the delete. As the text either side of the insert has been deleted in one operation, the other operations insert can be discarded as it was only relevant to the deleted text.
- When two pieces of text are inserted in the same location. This is difficult to prioritise, so I have tried to merge the text of the inserts where possible, and otherwise prioritised the insert from the initial operation.
This was challenging!! I have written something that works with what the tests I designed for it, so I think it is reasonably robust. That said, I think I may have over complicated it slightly. I would have liked to spend some time:
- Breaking up the long function into smaller testable units.
- Writing a load of integration tests to try and come up with strange scenarios (if I were to continue implementing this as an actual feature).
I was also hoping to include negative move values, but there is currently no functionality for figuring out how to move over what the other operation inserted in the middle of a negative move (see the failing test). I think to do this, it would be better to reorganise the original operation so that all moves are positive.