Quick Start

Apply Account

Here we demo how to connect to a MySQL data source, download PHP code and setup a online poll website similar to the Django Tutorial.

Apply for a new Tabilet account:

Tabilet Price Plans

For this demo, let's assume your new account has login name of tustin.

Development

Setup Data Source

After logging in, you should build a database schema by either adding it online or connecting to a remote data source. Let's assume you added a schema online. The database is named as tustin as well.

Two default roles will be created automatically at this point:

  • public role p, for a general visitor without a login
  • administrative role a, for a root or backend user

Add Tables

To support root login, we have automatically generated two tables tabilet_login_a and tabilet_login_a_tabilet_ip. Click on Database align the left hand menu bar to see them. Now let's add two more tables for polling questions and polling choices.

  • The table named poll_question stores our polling questions. Use the following for Create SQL:
    CREATE TABLE poll_question (
    	poll_id int NOT NULL AUTO_INCREMENT,
    	question varchar(255) NOT NULL,
    	created timestamp,
    	PRIMARY KEY (poll_id)
    );
    
  • The table named poll_choice stores the polling choices and their votes. Use the following for Create SQL:
    CREATE TABLE poll_choice (
    	choice_id int NOT NULL auto_increment,
    	poll_id int NOT NULL,
    	choice varchar(255) NOT NULL,
    	votes int NOT NULL default '0',
    	PRIMARY KEY  (choice_id),
    	FOREIGN KEY (poll_id) REFERENCES poll_question (poll_id) ON DELETE CASCADE
    );
    
Login Roles

Click on Project on the left menu bar, and Roles and Components will display for this project.

For this Quick Start, we don't need to add any more roles.

Add Components

Clicking on Components will list all components in this project. It is currently empty but let's add two new components.

Component question
  • use question as Name
  • use poll_question as Table
  • check both Public Default and Admin Default.
Component choice
  • use choice as Name
  • use poll_choice as Table
  • check neither Public Default nor Admin Default (because the defaults already set).

For public p in Access Control List

  • check Read to get individual question
  • check List to list all questions
  • check List to list all choices of a question
  • check Update to allow users to vote for a choice

    Later, we will show how to override Update by adding 1 to number votes

Note that administrative role a can act on any component for any action, so it is not listed.

Download

The software package for the project is now ready to be generated! Go to Software Package, and click Download.

Deployment

Where to Deploy?

We can deploy the project locally, in cloud or PaaS. Depending on the web server environment, we should adjust the system configuration accordingly. By default, we assume it is deployed to a local Windows 10 machine at localhost:80.

Useful Tip:

Deployment to the Linux platform is similar if not even easier to accomplish. You may also choose PaaS like Heroku or Amazon Elastic Beanstalk for easy web server and MySQL setups.

File Structure

Let's untar the download file, assumed to be C:/django, and check its content:

conf  config.json
      init.sql
logs  debug.log
src   choice     component.json
                 filter.php
                 model.php
      question   component.json
                 filter.php
                 model.php
      filter.php
      model.php
views a          choice     delete.html
                            edit.html
                            insert.html
                            startnew.html
                            topics.html
                            update.html
                 question   delete.html
                            edit.html
                            insert.html
                            startnew.html
                            topics.html
                            update.html
                 header.html
                 footer.html
                 login.html 
      p          choice     topics.html
                            update.html
                 question   edit.html
                            topics.html
                 header.html
                 footer.html
                 error.html
www   a          choice     edit.vue
                            startnew.vue
                            topics.vue
                 question   edit.vue
                            startnew.vue
                            topics.vue
                 footer.vue
                 header.vue
                 login.vue
      p          choice     topics.vue
                 question   edit.vue
                            topics.vue
                 footer.vue
                 header.vue
      app.html
      app.php
      genelet.js
      index.html
composer.json

We will explain this in more detail in Docs. For now, we only need to make two changes: 1, Modify config.json to match your local system environment; 2, Modify model.php in ./src/choice to reflect the voting counts.

Modify System Configurations

The original config.json looks like:

{
	"Document_root" : "/home/user/tabilet/tustin/www",
	"Project" : "Tustin",
	"Server_url" : "http://tustin.tabilet.com",
	"Script" : "/app.php",
	"Pubrole" : "p",
	"Template" : "/home/user/tabilet/tustin/views",
	"Uploaddir" : "/home/user/tabilet/tustin/www/upload",
	"Secret" : "ec2673c220f09490b942d178867dead7669528bbc4d651fc6bc8db55781d88b6be5c64f8e212d0e6fc8bd2710c527bf085c5",
	"Db" : ["mysql:host=localhost;dbname=tustin", "tustin", "aa7a2e36ef"],
	"Log" : {"Filename": "/home/user/tabilet/tustin/logs/debug.log", "Level": "info"},
	"Chartags" : {
		"html":{"Content_type":"text/html; charset='UTF-8'"},
		"json" : {
			"Content_type":"application/json; charset='UTF-8'",
			"Challenge":"challenge", "Logged":"logged", "Logout":"logout",
			"Failed":"failed", "Case":1
		}
	},
	"Roles" : {
		"a" : {
			"Is_admin" : true,
			"Id_name" : "a_id",
			"Attributes" : ["a_id", "a_login"],
			"Type_id" : 41,
			"Surface" : "ta",
			"Domain"  : "tustin.tabilet.com",
			"Duration": 86400,
			"Max_age" : 86400,
			"Secret"  : "d148598d88cd559ba20e838ac392cb09d525e20aeed9b7b0458fca990e53e96ad61a16ca2de53ddb759397b00caeeab1ac30",
			"Coding"  : "a35644e658d2f4d08bc4bcb595a99a7b5bccf5dbe156fe06013591320450464b5d4cb1ff6cbd41d430c55717d9e54ee3efc2",
			"Logout"  : "/",
			"Issuers" : {
				"db" : {
					"Default" : true,
					"Screen"  : 1,
					"Credential" : ["login", "passwd", "direct", "ta"],
					"Sql": "proc_tustin_a",
					"Out_pars": ["a_id", "a_login"]
				}
			}
		}
	}
}

Since the files are untarred onto C:/django, the document root should be changed to C:/django/www. The other changes are:

  • replace all instances of /home/user/tabilet/tustin with C:/django
  • replace all instances of tustin.tabilet.com with localhost (if your webserver runs on a Virtual Host, use that virtual host name)
  • in Db, replace the database name and user with your own
  • load init.sql, located in the same conf, into your database.

We should also add an administrator account. Assumming a LOGIN and PASSWORD, execute this SQL:

INSERT INTO tabilet_login_a (login, passwd, status, created) VALUES ('LOGIN', SHA1(CONCAT('LOGIN', 'PASSWORD')), 'Yes', NOW());

Useful Tip:

Logins are handled by the stored procedure proc_tustin_a. Passwords are saved as a SHA1 hash in the account table tabilet_login_a. All login attempts are stored in tabilet_login_a_tabilet_ip by IP addresses. If an IP address fails more than 5 times within the last hour, or more than 20 times within the last 24 hours, it will be blocked.

Modify Model

The default activity for update in REST actions is to update a table row with new values. When the public votes for a choice, assuming we have assigned this action to update, we expect vote counts to increase by 1.

We might think to use client-side Javascript to dynamically add 1 before sending it to the server, but this opens a serious security risk that allows client to send arbitrary votes. So instead, it's better to do it on the server-side.

Open model.php in C:\django\src\choice:
<?php
declare (strict_types = 1);
namespace Tustin\Choice;
use Tustin;
class Model extends \Tustin\Model
{
};
Th update, which increases votes by 1 when POST by p, will override the default. Running by a won't be affected.
<?php
declare (strict_types = 1);
namespace Tustin\Choice;
use Tustin;
class Model extends \Tustin\Model
{
    public function update(...$extra) : ?\Genelet\Gerror {
        if ($this->Get_rolename() == "p") {
            return $this->Do_sql(
               "UPDATE poll_choice SET votes=votes+1 WHERE choice_id=?", $this->ARGS["choice_id"]);
        }
        return parent::update(...$extra);
    }
};
Install Dependencies
The last step is to use composer to install all 3rd-party dependencies:
> cd C:/django
> composer install
> composer dump-autoload -o

Backend Administration

Login

The website http://localhost is now running. Open it in your browser. Go to the administrative portal

http://localhost/app.php/a/html/question?action=topics

Use LOGIN and PASSWORD which we set up before to login. The page is empty because there is no data.

Feed Data

In the URL, change action to startnew

http://localhost/app.php/a/html/question?action=startnew

This will open a starting page for us to input data.

Put Do you prefer iPhones or Androids? as the polling Question, and click Submit. We should get a confirmation page that action insert has been successful. You may go back to the topics page to confirm the new polling question.

Note that this question is automatically assigned poll_id=1.

Useful Tip:

In addition to the 5 standard REST verbs from the top class Genelet, Create (action=insert), Read (action=edit), List (action=topics), Update (action=update) and Delete (action=delete), we have added action=startnew as a landing page with a data input form.

Do the same for question choice. Go to

http://localhost/app.php/a/html/choice?action=startnew

to open a starting page to input question's choices.
  • iPhone: submit the form with poll_id=1, Choice=iPhone, and Votes=0.
  • Android: submit the form with poll_id=1, Choice=Android, and Votes=0.

Go to the topics page of choice, we will see that both the choices are set up correctly.

http://localhost/app.php/a/html/choice?action=topics

Public Votes

View Polls

Remember we have assigned List and Read on all questions, and List and Update on choices to the public? Now, public p can view all questions:

http://localhost/app.php/p/html/question?action=topics

and read one question:

http://localhost/app.php/p/html/question?action=edit&poll_id=1

and view all choices:

http://localhost/app.php/p/html/choice?action=topics

Vote

How do you allow your audience to vote on the different choices? Go to C:/django/views/p/choice/topics.html and add Vote id!, linked to action=update, to each item:

<tbody>{% for item in topics %}
<tr>
<td>{{ item.choice_id }}</td>
<td>{{ item.poll_id }}</td>
<td>{{ item.choice }}</td>
<td>{{ item.votes }}</td>
<td> <a href="choice?action=update&choice_id={{ item.choice_id }}">Vote it!</a> </td>
</tr>
{% endfor %}</tbody>

So the page

http://localhost/app.php/p/html/choice?action=topics

will look like this:

choices with vote link

Now click on Vote it! several times to confirm that vote counts increase as expected.

API and Vuejs

REST API Endpoints
Here are the API endpoints for the public to use
> List questions:
  GET http://localhost/app.php/p/json/question?action=topics
  or GET http://localhost/app.php/p/json/question
> Read a question:
  GET http://localhost/app.php/p/json/question?action=edit&question_id=ID
  or GET http://localhost/app.php/p/json/question/ID
> List choices:
  GET http://localhost/app.php/p/json/choice?action=topics
  or GET http://localhost/app.php/p/json/choice
> Update (vote) a choice:
  POST http://localhost/app.php/p/json/choice {action=update&choice_id=ID}
Vuejs' One-page Website

We have also created an one-page website based on the API and Vuejs. Please follow the links on the front page http://localhost.

Continue to Documents

Privacy Policy       Terms of Use