ro31337 / libretaxi Goto Github PK
View Code? Open in Web Editor NEWOpen source Uber #deleteuber
Home Page: https://t.me/libretaxi_bot
License: GNU Affero General Public License v3.0
Open source Uber #deleteuber
Home Page: https://t.me/libretaxi_bot
License: GNU Affero General Public License v3.0
UserStateResponse
is type of Response
with the type user-state
, and will be used to tell the handler to update (current) user's state. Constructor accepts hash of properties to be set.
For example, SelectLanguage
action can return this response, and user.state.locale
will be set to en
. The code of SelectLanguage.execute
method may look like:
// ...
return new UserStateResponse({ locale: 'en' });
However, the code above is not a good practice. UserStateResponse
is the base class for all state operations. For example, the line above can be rewritten to:
return new SelectLanguageResponse('en');
And SelectLanguageResponse
extends UserStateResponse
:
class SelectLanguageResponse extends UserStateResponse ...
Logging system should output logs to console and to log file. Date and time for each log line should be specified along with log level, e.g. [2016-03-29 10:45:59] [info] Log line goes here
.
validated-stateful-key.js
has ValidatedStatefulKey
, but during refactoring it turned out that this name doesn't reflect the function of this type anymore. So it can be refactored to something more generic and reused later (potential reuse is user.js
).
Suggested name checkPlatformType
.
User should be stateful and this syntax should work:
const user = await new User({ platformType: 'cli', platformId: '1' }).load();
user.state.location = 'something';
user.save();
StateStorage keeps state data, updates state when underlying storage updates. Objects in application can be stateful. StateStorage is basic structure that implements persistent state (won't go away on app restart).
Key for constructor below is already existing new StateKey(...).toString()
- something that identifies particular command for particular user for particular platform.
StateStorage implementation:
constructor(key)
- Constructor. key
- state key, for example telegram_31337_4566bd48-d369-4594-aa1e-fb5dae1acf43
.
load()
- Loads state from the actual storage. Returns promise which is executed when changes were initially loaded.
save()
- updates the storage with current state, returns promise. Will only overwrite the children enumerated in state
.
setState({ ... })
- updates selected properties.
state
- actual state.
dispose
- unsubscribes itself from storage updates (off
method for Firebase).
Usage example:
new StateStorage('telegram_31337_4566bd48-d369-4594-aa1e-fb5dae1acf43')
.load()
.then((storage) => {
console.dir(storage.state); // => {}
storage.setState({ a: 1, b: 2 });
storage.save().then(() => {
console.log('saved');
})
});
The following dependencies are satisfied by their declared version range, but the installed versions are behind. Install the latest versions with modifying package file.
babel-cli ^6.7.5 → ^6.10.1
babel-eslint ^6.0.3 → ^6.1.1
babel-plugin-syntax-async-functions ^6.5.0 → ^6.8.0
babel-plugin-transform-regenerator ^6.6.5 → ^6.9.0
babel-preset-es2015 ^6.6.0 → ^6.9.0
babel-preset-stage-2 ^6.5.0 → ^6.11.0
babel-register ^6.7.2 → ^6.9.0
esdoc ^0.4.6 → ^0.4.7
i18n ^0.8.2 → ^0.8.3
moment ^2.13.0 → ^2.14.1
nyc ^6.4.0 → ^6.6.1
object-assign ^4.0.1 → ^4.1.0
loudRejection/api is deprecated
is displayed. Explained here. Updating ava
should fix the issue.
SelectLanguageResponse
is subclass of UserStateResponse that represents data transfer object that keeps selected language. Response handler (to be implemented later) will save language to user's state.
class SelectLanguageResponse extends UserStateResponse ...
Usage example:
return new SelectLanguageResponse('en');
Depends on #50
Documentation for toString
method, StateKey
class is missing. It must be added. Also, esdoc should be checked for other errors.
checkNotNull
validating mixin should be refactored so it will work without parameters. If parameters are not specified, mixin should just check if object is provided.
OptionsResponse
is type of Response
with the type options
. Constructor accepts array of rows, where each row is array of option
objects. This allows to display options to the user in a grid format like:
row 1: One | Two | Three
row 2: OK | Cancel
Implementation of the above:
[
[{ label: 'One', value: '1' }, { label: 'Two', value: '2' }, { label: 'Three', value: '3' }],
[{ label: 'OK', value: 'ok' }, { label: 'Cancel', value: 'cancel' }]
]
option
object:
{ label: 'One', value: '1' }
where label
is label to display, and value
is value that will be passed to execute
method of Action
. In the example above OptionsResponse can generate 5 values:
1
2
3
ok
cancel
Later Action.execute
must handle these five values.
Note: representation of option
object for Telegram is slightly different: { text: '...', callback_data: '...' }
(see here). Later Telegram decorator can be applied.
Logging system should have LOG_FILE
parameter in .env
. Logging system should be configurable for tests. It should be silent by default with option to enable logging to console and/or file. Log filenames for test outputs can be hardcoded, but must be git-ignored.
It also may include dotenv
configuration.
Refactor StateStorage
into Stateful
mixin that can be applied to any class. Usage example:
class User extends Stateful(ValidatedUser) {
constructor() {
....
this.stateful = {
table: 'users',
key: new UserKey(platformType, platformId).toString(); // cli_1
};
}
}
or
class Command extends Stateful() {
constructor() {
....
this.stateful = {
table: 'commands',
key: new StateKey(platformType, platformId, guid).toString(); // cli_1_guid
};
}
}
or
class Foo extends Stateful() {
constructor() {
...
this.stateful = {
table: 'foos',
key: '1'
};
}
}
Stateful Foo
usage example:
const foo = new Foo();
foo.load().then((foo) => {
foo.state.bar = 123; // set bar property of "state" object
foo.save();
});
Stateful mixin implementation will look like:
let Stateful = (superclass) => class extends superclass {
load() {
// ...
}
}
"connection string for Firebase for stateful objects." --> "Firebase connection string for stateful objects"
Initial infrastructure should have:
.env-sample
file. .env
should be added to .gitignore
npm start
and npm test
commands to run and test applicationnpm i
, copy .env-sample
to .env
, npm start
), link to telegram bot in README file.OptionsResponse
contains constructor validation. It should be moved into a separate file.
Action (or "menu action") represents the action that should be executed when action is selected (by redirect or any other way).
Action can be executed in two ways:
post
method)get
method)All actions should have unique identifier so they can be stateful (keep it's state in the database through stateful
mixin).
Actions are common way of user interaction, so they should have initialized i18n
object.
Update DEVELOPMENT.md
and explain why 127.0.0.1 localhost.firebaseio.test
should be added to /etc/hosts
.
See https://github.com/urish/firebase-server/blob/master/README.md
Existing ValidatedResponse
class must be converted to validation mixin.
Response
itself should be defined as
import Validation from './response-validation`;
class Response extends mix(Object).with(Validation) { ... }
Only signature of Response
class should be changed (no any internal modifications).
See for demo https://github.com/ro31337/es6-constructor-decorators-demo
ErrorResponse
is type of Response
with the type error
and represents the error message. It contains only error message. Error response is not exception, is just a way to tell user that something goes wrong inside of menu action. For example, somehow invalid option was selected. Then error message is generated. It can be displayed to the user with error icon.
StateKey
was designed for StateStorage
(Stateful
now) and now should be slightly modified:
StatefulKey
guid
should be optionalIf guid
is not specified, key should be generated the following way:
platformtype_platformid
For example:
cli_1
or
telegram_31337
StatefulKey
can be used for actions (have guids), users (don't have guids), commands (may have guids).
Esdoc should be integrated into the project. It also includes adding simple comments, updating .gitignore
for documentation directory, updating README.md
to mention doc generating process.
Also, DEVELOPMENT.md
needs to be added. Format of esdoc comments should be explained along with development tools configuration.
Depends on:
User entity should be created with the following properties:
platformType
- type of the platform. Examples: cli
for console application, telegram
for Telegram, whatsapp
for Whatsapp, etc. cli
should be supported by default, others can be added now or later.platformId
- user identified for the platform. Telegram user, for instance, may have id
set to random value like 123456
.So we'll have the set of properties that will represent the user, regardless of the platform. Moreover, we'll be able to connect two users from different platforms.
Basic validation should be implemented.
TextResponse
constructor contains validation (checking message
parameter for null). It should be replaced with checkNotNull
validating mixin.
StatefulKey
has validation that can be replaced with checkNotNull
mixin. It should be refactored.
Add and configure statical code analysis: linting tool and editor configurations.
OptionsResponse must be improved. Now it's quite hard to read when you want to initialize it:
const response = new OptionsResponse({
rows: [
[{ label: 'One', value: '1' },{ label: 'Two', value: '2' },{ label: 'Three', value: '3' }],
[{ label: 'OK', value: 'ok' },{ label: 'Cancel', value: 'cancel' }]
]
});
It can be simplified by introducing two methods row()
and add(...)
. For example:
const response = new OptionsResponse()
.row().add('One', 1).add('Two', 2).add('Three', 3)
.row().add('OK', 'ok').add('Cancel', 'cancel');
or
const response = new OptionsResponse().row();
response = response.add('One', 1);
response = response.add('Two', 2);
Note: there is no new
keyword, because row
and add
return new objects.
StateKey
is object that represents the key for State object. There can be unlimited number of states (each state object is just a hash). Key is unique identifier used to to retrieve particular state (hash) from the storage.
It should have constructor that accepts parameters:
platformType
- type of the platformplatformId
- unique user identifier for this particular platformguid
- guid of the object that is using stateIdea is that state can be mixed into any object (as state
property). And each menu action for particular user should have its own state. Menu actions can't have the same state for all users (that's why we can't have one guid
parameter). State is not shared between users and between commands.
StateKey
must have toString
method that returns string representation of the key in the following format:
platform_userid_guid
Example:
telegram_31337_4566bd48-d369-4594-aa1e-fb5dae1acf43
Key should be lowercased.
There is .env-sample
in project root directory that contains required fields for program to operate properly. Enginner supposed to copy .env-sample
to .env
after pulling the project from source code repository. .env
is git-ignored, because the main idea is not to commit production values.
But there is need for .env
file for tests. This .env
file should be located in test
directory, should not be git-ignored, and should have test parameters. Also, parameter FIREBASE_STATES_CONNSTR
should be added to both test/.env
and .env-sample
- to be used later.
checkNotNull
validating mixin works, but testing classes containing this mixin can be simplified. Helper method should be introduces. Syntax example:
checkNotNullTest(['foo', 'bar'], () => {
return MyClass;
});
or for single property:
checkNotNullTest('foo', () => {
return MyClass;
});
return MyClass
intentionally has no new
keyword, because internally, inside of checkNotNullTest
, new object (that needs to be tested) will be constucted with new
keyword and hash of parameters:
{ foo: true, bar: true }
or
{ foo: true }
User validation (ValidatedUser
) can be now refactored. There is no any sense to have this validation as a separate class when we have checkNotNull
and checkPlatformType
validating mixins. So User
class signature can be changed to use these mixins, and ValidatedUser
can be removed from app.
Create CLI options response handler. From documentation:
Options response is response that contains rows of options. Each row is array. Each option in the row is object with
label
andvalue
properties set.
Example:
const r = new OptionsResponse({
rows: [
[{ label: 'One', value: '1' },{ label: 'Two', value: '2' },{ label: 'Three', value: '3' }],
[{ label: 'OK', value: 'ok' },{ label: 'Cancel', value: 'cancel' }]
]
});
// response above represents the following structure:
// row 1: One | Two | Three
// row 2: OK | Cancel
In CLI all options should be flattened (only one row).
Inquirer.js is a good choice to display the list of options. See example
Only one option can be selected.
esdoc
script should be fixed the way it will NOT open browser with documentation after npm run esdoc
.
Concept is to build menu tree similar to other bots like Botan. Example:
When Settings clicked:
Create sample menu tree with the following menu structure
0.1.0 -+- no_type -+- select_type
| +- catch_all
|
+- client -+- request taxi
| +- settings
| +- catch_all
|
+- driver -+- imhere
+- settings
+- catch_all
catch_all
- command to catch all other requests.
imhere
- menu item that drivers should pick every hour to update their current location
0.1.0
- menu version. Will be adjusted according to SEMVER.
Client menu location is array of menu ids:
['0.1.0', 'no_type', 'select_type']
Lines above are default menu location for the user. /start
command will just execute command with id select_type
.
Example of executing command with default location:
cmd = menu.handler;
cmd.execute();
QUESTIONABLE:
buildCommand
constructs Command
object. Constructor accepts two parameters:
context
- see belowstate
- state of the object, fetched from the menu.context
has the following properties:
user
- user objecti18n
- object for translations, initialized with locale
property in user
objectrequest
- request from the user. If it's empty, it means that menu item has been just picked and command should reply with what is expected.response
- callback that accepts two parameters: state and text which needs to be sent to the user. This callback updates state in the database, and when successful, sends response to the user. It can also update new menu location.See also:
Add a script that will run before all the tests. This script should check if localhost.firebaseio.test
resolves to 127.0.0.1
. If not - it should display message and prevent running the tests suite.
Redirect response is a type of response (along with text
, menu
, composite
, etc.) which is used to redirect users to certain menu location. It accepts only one parameter - path
, which can be absolute (starts with /
) or relative (starts with .
). It should always ends with .js
. Redirect response should throw exceptions if it's malformed. Redirect response doesn't verify the presence of the actual file on the file system, because path can be relative. And redirect response knows nothing about menu structure and handlers location on file system.
Error is here https://github.com/ro31337/cheaptaxi/blob/bb44e6ba22f92a7bc47c7e9cfcc3df650441bec2/src/stateful.js#L27
Keyword @extends
is not applicable here. And should be removed.
Subdirectories are not included by mistake. So files from ./test/
are included, but from ./test/validations
are not. It should be fixed, plus failing tests (if any) should be fixed.
Not null validating mixing is generic validating mixin that can be used anywhere across the program. The main idea is to avoid creating multiple validation mixins when only null check is required.
For example:
if (!opts.message) {
throw new ArgumentError('message parameter not specified');
}
^^^ actual code from text-response.js
.
Moving this functionality to validated-text-response.js
is redundant, because it just validates one property. To speed up development new mixin should be introduced.
Here is how it can work:
export default class TextResponse extends mix(Response).with(CheckNotNull('message'))
CheckNotNull
can be imported standard way:
import CheckNotNull from './check-not-null-validation.js';
The code above will mix TextResponse
with CheckNotNull
validating mixin instead of creating new class. This validating mixin can be reused in other places.
CheckNotNull
must accept parameters of two types: String
and Array
. If parameter is the type of String
, it will check only one property. If Array
- the list of properties.
Version 1.0.0
is not recommended. Official SEMVER recommendation is:
The simplest thing to do is start your initial development release at 0.1.0 and then increment the minor version for each subsequent release.
It also needs to be updated to use esdoc
tags like @since
in the future.
Eslint 3 was released. Package must be updated.
See migrating guide.
There is no such thing as handler in existing application, so one should be added. Handler is short for "response handler". We have many types of responses:
For each response we may have response handler. Handler will accept Response in it's constructor arguments ({ response: ... }
). Handle process can be triggered by call
method.
We have multiple platforms (telegram
, cli
), and each platform can have it's own response handler. For example, text response handler will send a message in case of telegram
platform, and print the message to console for cli
platform.
When the message sent to telegram platform, the result in most cases will came from the user who is using Telegram application. In this case it's easy, and state machine will be able to handle it correctly.
But when the message is sent to cli
platform, we usually expect some kind of user interaction (for text response there probably won't be any interaction, but for options response there should be interaction). So call
method for cli
platform will have the following signature:
call(onResult) { ... }
where onResult
is optional callback, and can be executed when there is result (choice from the list of options). And can be skipped (for example, if it's text message only).
Folders should be organized the following way:
|- /response-handlers
| |- /cli
| | |- text-handler.js
| |- /telegram
| | |- text-handler.js (to be implemented later)
| |- handler.js
In this story handler.js
should be implemented as abstract class, and text-handler.js
should be implemented as concrete implementation for cli
.
ActionFactory.fromLocation
(or Action.fromLocation
) factory method should be introduced. It will accept user.state.location
parameter.
location
is pointing to the relative path of .js
file inside of src/menu
folder. Factory method should import and create instance of class from result location.
For example, if location
is select-user-type.js
, file from ./src/menu/select-user-type.js
should be loaded, and default class from this file should be instantiated. It's enough for now, later other functionality can be added - when Action
will be introduced.
Also, empty/undefined location should be resolved to default menu item, which is now select-language.js
(it's okay to have stub for now).
Response
has ValidatedResponse
mixin. It can be refactored and cleaned up. It requires parameter type
and checkNotNull
mixin can be used instead of code in existing validating mixin.
Project initially should have translation infrastructure, so everything can be translated into many languages. For particular translation item description should be provided. Description should have description of the context where the phrase is used. This information will be used by translators (real persons) to help them understand the context of particular phrase without digging into application.
Example:
phrase: "Welcome to our system! Type /help for the list of commands"
language: "en"
desc: "This is a banner. This banner is displayed every time to new users when they add the bot to their contacts and type /start".
Description exists in only one language - English.
mix
is used in many places:
mix(Dummy)...
mix(Object)...
It should be replaced with mix(class {})
, this syntax doesn't require class to be defined. And mix(Object)
has side effects -- class functions don't work.
Response
should be implemented as another very essential brick of program architecture. Response is something Action
should return. Response can be of type text
, or json
, or composite
, or telegram_menu
(text
response should be only implemented for now). Response handler(s) will process responses based on response type. For response with type text
, a message
will be returned to the user.
At least two object should be implemented:
Response
- abstract class for response, has type
property.TextResponse
- type
property set to text
, has message
.While atomic responses are useful, it's often required to return composite response from the action. For example, on post
action can return three responses:
return new CompositeResponse()
.add(new TextResponse(`You selected ${value}`))
.add(new SelectLocaleResponse(value))
.add(new RedirectResponse(...)); // TODO
First response will force handler to send the text to the user. Second is used to update state. And third is used to redirect the user to new url.
CompositeResponse
must be implemented. It should have add
method and responses
property, representing array of responses.
.env-sample
should be updated, because it contains no information about LOG_FILE
. It should be set to cheaptaxi.log
by default.
Redirect response can be simplified, because ActionFactory
can create actions only from existing map (routes). See here.
routes
should exist in separate file. ActionFactory
should import routes. Redirect response should validate in constructor that route actually exists.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.