/docsync_server

A backend for storing and syncing documents

Primary LanguageTypeScript

docsync_server

A backend service for storing and manipulating and syncing documents

Repo Structure

.
├── README.md                 -- You are reading this now
├── package.json              -- NodeJS dependencies
├── yarn.lock
├── serverless.yml            -- Serverless configuration
├── src
│   ├── entities              -- Data models
│   │   └── documents.ts      -- Data models dealing with documents
│   ├── handlers              -- Lambda handler functions
│   │   ├── connection.ts     -- connection / disconnection
│   │   ├── documents.ts      -- document operations
│   │   └── message.ts        -- message routing
│   └── utils
│       └── aws.ts            -- AWS utility functions
├── tests                     -- TODO

Infrastucture dependencies

The intrastructure dependencies are described in serverless.yml. Currently 2 DynamoDB tables are provisioned by serverless.

  • connectionTable: Stores the connection IDs used for messaging the clients connected over WebSockets
  • docsTable: Stores document data

Setup

Serverless setup

  1. Install sls tool:
    brew install serverless
  2. clone repo
    git clone <repo_url>
  3. deploy to aws
    sls deploy

Local development

  1. Install dependencies
    yarn install
  2. Invoke functions
 CONNECTION_TABLE=connectionTable DOCUMENTS_TABLE=docsTable sls invoke local --function default --data '{ "requestContext": { "routeKey": "$default", "messageId": "Yz5MEf7dIAMCJfw=", "eventType": "MESSAGE", "extendedRequestId": "Yz5MEE6QoAMFusQ=", "requestTime": "21/Sep/2022:12:50:59 +0000", "messageDirection": "IN", "stage": "dev", "connectedAt": 1663764639475, "requestTimeEpoch": 1663764659493, "identity": { "sourceIp": "94.21.159.157" }, "requestId": "Yz5MEE6QoAMFusQ=", "domainName": "i5lj1y1eui.execute-api.us-east-1.amazonaws.com", "connectionId": "Yz5I8fWGIAMCJfw=", "apiId": "i5lj1y1eui" }, "body": "{\"operation\": \"LIST\"}", "isBase64Encoded": false }'

(To initialize blank project)
sls create --template aws-nodejs-typescript --path

Data Models

Document:
  - documentId: UUID -- identified, auto generated on creation)
  - name: string     -- name of document
  - content: string  -- content of document

Message protocol

After connecting to the service over websocket, clients have to use a custom message protocol for document operations.

Theres a special key always required in the message: operation: <GET|LIST|CREATE|UPDATE|DELETE>, which specifies the type of document operation. Payload is dependent on the operation.

Examples:

List documents Returns all documents.
# LIST DOCUMENTS
Client request ->
{
    "operation": "LIST"
}

Server response <-
{
    "documents": [
        {
            "name": "mydoc",
            "content": "<html><body>Hello</body></html>",
            "documentId": "ea7b41c0-08db-41b6-af95-973640a68a6e"
        },
        ...
}

Get document Returns a single document.
# GET DOCUMENT
Client request ->
{
    "operation": "GET",
    "documentId": "0935fae2-2d97-4da5-a97e-b3e2f6ccdbcd"
}

Server response <-
{
    "document": {
        "name": "mydoc",
        "content": "<html><body>Hello</body></html>",
        "documentId": "0935fae2-2d97-4da5-a97e-b3e2f6ccdbcd"
    }
}
Create document Creates a document, `documentId` will be autogenerated.
# CREATE DOCUMENT
Client request ->
{
    "operation": "CREATE",
    "document": {
        "content": "<html><body>New shiny document</body></html>",
        "name": "shiny_doc"
    }
}

Server response <-
{
    "operation": "CREATE",
    "document": {
        "name": "shiny_doc",
        "content": "<html><body>New shiny document</body></html>",
        "documentId": "9b4d6c25-011d-4438-ab45-61c29242e438"
    }
}

Update document Updates the document if matching `documentId` is found. Otherwise inserts a new document.
# UPDATE DOCUMENT
Client request ->
{
    "operation": "UPDATE",
    "document": {
        "content": "<html><body>Updated doc</body></html>",
        "name": "Updated doc",
        "documentId": "81d53e7f-28a7-442a-bfbb-627bea8327d0"
    }
}

Server response <-
{
    "operation": "UPDATE",
    "document": {
        "name": "Updated doc",
        "content": "<html><body>Updated doc</body></html>",
        "documentId": "81d53e7f-28a7-442a-bfbb-627bea8327d0"
    }
}

Delete document Deletes document with `documentId`.
# DELETE DOCUMENT
Client request ->
{
    "operation": "DELETE",
    "documentId": "d1957b9c-057e-431a-9641-b463dfdfc007"
}

Server response <-
{
    "operation": "DELETE",
    "document": "d1957b9c-057e-431a-9641-b463dfdfc007"
}

Broadcast events

Upon write operations, broatcast message is emitted to all clients, so the clients can act upon the messages and update their state.

Document creation A document was created. `document` key contains the document object the clients should store internally.
{
    "operation": "CREATE",
    "document": {
        "name": "a",
        "content": "<html><body>New shiny document</body></html>",
        "documentId": "9b4d6c25-011d-4438-ab45-61c29242e438"
    }
}
Document update A document was updated. `document` key contains the new state of document object the clients should update internally.
{
    "operation": "UPDATE",
    "document": {
        "name": "a",
        "content": "<html><body>Updated document</body></html>",
        "documentId": "9b4d6c25-011d-4438-ab45-61c29242e438"
    }
}
Document deletion A document was deleted. Document with `documentId` does not exist anymore, the clients should update their state accordingly.
{
    "operation": "DELETE",
    "document": "d1957b9c-057e-431a-9641-b463dfdfc007"
}