World Conquest Chronicles

World Conquest Chronicles

Romani Mind, v1.0

Web service for a handling of a business logic of conversations.

Major release. Load a handler class from a specified file and simplify the interface of a handling.

Change Log

  • Handler:
    • Load a handler class from the specified file:
      • support a loading from any place;
      • reload the handler class on each request.
    • Split a request handling to a start of a conversation and a processing of new messages.
    • Receive a default state of a conversation from the handler class.
    • Add an abstract class for the handler class.
  • Request:
    • Fix the JSON Schema of a request:
      • Use an integer type for timestamp fields.
      • Check that all integer fields are positive.
    • Transform a request from a dictionary to an object.
    • Pass a request to a handler by top level parts.
    • Reverse an order of messages in a request.
    • Parse timestamps in a request.
    • Add a stuff for the Postman tool.
  • Response:
    • Combine a response from top level parts returned by a handler.
    • Automatically detect types of messages based on their content.

Features

  • working mode:
    • run as a web service;
    • control via the RESTful API;
    • handle a conversation by the specified outer handler class (see below for details);
  • commands:
    • handle a part of a history of a thread with an user:
      • request data:
        • list of messages;
        • user info;
        • arbitrary auxiliary data;
      • response data:
        • list of messages;
        • probably updated arbitrary auxiliary data;
  • handler:
    • load the handler class (see below for details) from the specified file:
      • support a loading from any place;
      • reload the handler class on each request;
    • split a request handling to a start of a conversation and a processing of new messages;
    • receive a default arbitrary auxiliary data from the handler class (see below for details);
    • request:
      • pass a request to the handler by top level parts (an user info, an arbitrary auxiliary data and a list of messages);
      • reverse an order of messages in a request;
    • response:
      • combine a response from top level parts returned by the handler (an arbitrary auxiliary data and a list of messages);
      • automatically detect types of messages based on their content.

Command

Command format in the JSON Schema format:

{
    "type": "object",
    "required": ["messages", "user", "state"],
    "additionalProperties": false,
    "properties": {
        "messages": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["id", "type", "timestamp", "text"],
                "additionalProperties": false,
                "properties": {
                    "id": {"$ref": "#/definitions/numeric_string"},
                    "type": {"$ref": "#/definitions/alphabetical_string"},
                    "timestamp": {"$ref": "#/definitions/positive_integer"},
                    "text": {"type": "string"}
                }
            }
        },
        "user": {
            "type": "object",
            "required": [
                "id",
                "category",
                "username",
                "full_name",
                "gender",
                "birthday",
                "biography",
                "location",
                "counters",
                "flags"
            ],
            "additionalProperties": false,
            "properties": {
                "id": {"$ref": "#/definitions/numeric_string"},
                "category": {"type": "string"},
                "username": {"type": "string"},
                "full_name": {"type": "string"},
                "gender": {"$ref": "#/definitions/positive_integer"},
                "birthday": {"type": "string"},
                "biography": {"type": "string"},
                "location": {
                    "type": "object",
                    "required": ["geo", "address"],
                    "additionalProperties": false,
                    "properties": {
                        "geo": {
                            "type": "object",
                            "required": ["latitude", "longitude"],
                            "additionalProperties": false,
                            "properties": {
                                "latitude": {"type": "number"},
                                "longitude": {"type": "number"}
                            }
                        },
                        "address": {
                            "type": "object",
                            "required": ["street", "city", "zip", "country_code"],
                            "additionalProperties": false,
                            "properties": {
                                "street": {"type": "string"},
                                "city": {"type": "string"},
                                "zip": {"type": "string"},
                                "country_code": {"$ref": "#/definitions/positive_integer"}
                            }
                        }
                    }
                },
                "counters": {
                    "type": "object",
                    "required": ["media", "followers", "followings"],
                    "additionalProperties": false,
                    "properties": {
                        "media": {"$ref": "#/definitions/positive_integer"},
                        "followers": {"$ref": "#/definitions/positive_integer"},
                        "followings": {"$ref": "#/definitions/positive_integer"}
                    }
                },
                "flags": {
                    "type": "object",
                    "required": ["is_private", "is_business", "is_verified"],
                    "additionalProperties": false,
                    "properties": {
                        "is_private": {"type": "boolean"},
                        "is_business": {"type": "boolean"},
                        "is_verified": {"type": "boolean"}
                    }
                }
            }
        },
        "state": {}
    },
    "definitions": {
        "positive_integer": {"type": "integer", "minimum": 0},
        "numeric_string": {"type": "string", "pattern": "^\\d+$"},
        "alphabetical_string": {"type": "string", "pattern": "^[a-z_]+$"}
    }
}

Example:

{
  "state": null,
  "user": {
    "id": "7580950755",
    "category": "",
    "username": "thewizardplusplus",
    "full_name": "",
    "gender": 0,
    "birthday": "",
    "biography": "",
    "location": {
      "geo": {
        "latitude": 0,
        "longitude": 0
      },
      "address": {
        "street": "",
        "city": "",
        "zip": "",
        "country_code": 0
      }
    },
    "counters": {
      "media": 3,
      "followers": 0,
      "followings": 0
    },
    "flags": {
      "is_private": false,
      "is_business": false,
      "is_verified": false
    }
  },
  "messages": [
    {
      "id": "28205430427220448756392777616457728",
      "type": "media",
      "timestamp": 1529019447254058,
      "text": ""
    },
    {
      "id": "28205430242905285891625733805047808",
      "type": "text",
      "timestamp": 1529019437262313,
      "text": "Test two."
    },
    {
      "id": "28205429958923490047994478895038464",
      "type": "media",
      "timestamp": 1529019421867629,
      "text": ""
    },
    {
      "id": "28205429795094598621958535854948352",
      "type": "text",
      "timestamp": 1529019412986447,
      "text": "Test one."
    }
  ]
}

API

API description in the Swagger format:

openapi: '3.0.0'
info:
  title: romani-mind
  version: 1.0.0
servers:
- url: http://localhost:{port}/api/v1
  variables:
    port:
      default: '7070'
paths:
  /history:
    post:
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/request'
      responses:
        200:
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/response'
        400:
          $ref: '#/components/responses/error'
        404:
          $ref: '#/components/responses/error'
        405:
          $ref: '#/components/responses/error'
        500:
          $ref: '#/components/responses/error'
components:
  schemas:
    positive_integer:
      type: integer
      minimum: 0
    numeric_string:
      type: string
      pattern: ^\\d+$
    alphabetical_string:
      type: string
      pattern: ^[a-z_]+$
    request_message:
      type: object
      required:
      - id
      - type
      - timestamp
      - text
      properties:
        id:
          $ref: '#/components/schemas/numeric_string'
        type:
          $ref: '#/components/schemas/alphabetical_string'
        timestamp:
          $ref: '#/components/schemas/positive_integer'
        text:
          type: string
    geo:
      type: object
      required:
      - latitude
      - longitude
      properties:
        latitude:
          type: number
        longitude:
          type: number
    address:
      type: object
      required:
      - street
      - city
      - zip
      - country_code
      properties:
        street:
          type: string
        city:
          type: string
        zip:
          type: string
        country_code:
          $ref: '#/components/schemas/positive_integer'
    location:
      type: object
      required:
      - geo
      - address
      properties:
        geo:
          $ref: '#/components/schemas/geo'
        address:
          $ref: '#/components/schemas/address'
    user:
      type: object
      required:
      - id
      - category
      - username
      - full_name
      - gender
      - birthday
      - biography
      - location
      - counters
      - flags
      properties:
        id:
          $ref: '#/components/schemas/numeric_string'
        category:
          type: string
        username:
          type: string
        full_name:
          type: string
        gender:
          $ref: '#/components/schemas/positive_integer'
        birthday:
          type: string
        biography:
          type: string
        location:
          $ref: '#/components/schemas/location'
        counters:
          type: object
          required:
          - media
          - followers
          - followings
          properties:
            media:
              $ref: '#/components/schemas/positive_integer'
            followers:
              $ref: '#/components/schemas/positive_integer'
            followings:
              $ref: '#/components/schemas/positive_integer'
        flags:
          type: object
          required:
          - is_private
          - is_business
          - is_verified
          properties:
            is_private:
              type: boolean
            is_business:
              type: boolean
            is_verified:
              type: boolean
    request:
      type: object
      required:
      - messages
      - user
      - state
      properties:
        messages:
          type: array
          items:
            $ref: '#/components/schemas/request_message'
        user:
          $ref: '#/components/schemas/user'
        state:
          nullable: true
    response_message:
      type: object
      oneOf:
      - required:
        - type
        - message
        properties:
          type:
            type: string
            enum:
            - message
          message:
            type: string
      - required:
        - type
        - filename
        properties:
          type:
            type: string
            enum:
            - photo
          filename:
            type: string
    response:
      type: object
      required:
      - messages
      - state
      properties:
        messages:
          type: array
          items:
            $ref: '#/components/schemas/response_message'
        state:
          nullable: true
  responses:
    error:
      description: Some Error
      content:
        text/plain:
          schema:
            type: string

Architecture

UML component diagram in the PlantUML language:

@startuml
skinparam componentStyle uml2

[Thinker Class]

package "Romani Mind" {
  [Web Server] -up- API
  [Web Server] .down.> [History Handler] : history

  [History Handler] .down.> [Thinker Class] : get_default_state()
  [History Handler] .down.> [Thinker Class] : on_start()
  [History Handler] .down.> [Thinker Class] : on_new_messages()
}

[Romani Core] .down.> API : POST /history

@enduml

Rendered UML component diagram:

Abstract Class for a Handler

User = NewType('User', NamedTuple)
JSON = Union[None, bool, int, float, str, Sequence['JSON'], Mapping[str, 'JSON']]
State = JSON
Message = NewType('Message', NamedTuple)
History = Sequence[Message]
Response = Tuple[State, Sequence[str]]

class Thinker(ABC):
    def get_default_state(self) -> State:
        return None

    @abstractmethod
    def on_start(self, user: User, state: State) -> Response:
        pass

    @abstractmethod
    def on_new_messages(self, user: User, state: State, history: History) -> Response:
        pass

Screenshots

POST /history

POST /history endpoint in the Postman tool with a start of a conversation

POST /history endpoint in the Postman tool with a request data from environment variables

POST /history endpoint in the Postman tool with the directly specified request data