Page 1 of 1

AppGini API

Posted: 2020-12-13 02:27
by sathukorala
This has been discussed in two threads
viewtopic.php?f=8&t=3996
viewtopic.php?f=8&t=4050

but I want to discuss it in detail in this thread (jsetzer has contributed a lot)

Rafael Carneiro de Moraes has designed an API which is published in Github
https://github.com/rafinhacarneiro/appgini-api

But unfortunately, it seems to be not working with updated AppGini. It had V2 and V3 versions and both functions give broken results
The V2 is simple which containing the below code block

Code: Select all

<?php

    // Defines the JSON UTF-8 response format
    header("Content-Type: application/json;charset=utf-8");

    class api{

        /* -------- PROPERTIES -------- */
        
        // Admin user data
        private $tokens = array();
        // Request data
        private $request = array();

        // Database mirror
        private $base = array();

        // API response
        public $report = array();
        public $meta = array();

        /* -------- METHODS -------- */
        
        function __construct(){

            // AppGini integration
            $appGiniPath = $this -> getAppGiniLib();

            // If the AppGini (lib.php) were found, continues
            if( $appGiniPath ){

                include $appGiniPath;
    
                // Fetches the Admins' info
                $sql = "SELECT memberID, passMD5
                        FROM membership_users
                        WHERE groupID = 2";
    
                $query = sql($sql, $eo);
    
                $users = array();
    
                while($res = db_fetch_assoc($query)){
                    $memberID = strtolower($res["memberID"]);
                    $passMD5 = $res["passMD5"];
    
                    $users[$memberID] = $passMD5;
                }
    
                $this -> tokens = $users;
    
                // Fetches an app's database mirror
                $sql = "SELECT
                            t.TABLE_NAME AS tbl,
                            GROUP_CONCAT(DISTINCT REPLACE(c.COLUMN_NAME, '?=', '') SEPARATOR '|') AS cols
                        FROM INFORMATION_SCHEMA.TABLES t
                        INNER JOIN INFORMATION_SCHEMA.COLUMNS c
                            ON c.TABLE_NAME = t.TABLE_NAME
                        WHERE
                            t.table_schema = '{$dbDatabase}' AND
                            c.COLUMN_NAME NOT LIKE 'field%'
                        GROUP BY t.TABLE_NAME
                        ORDER BY t.TABLE_NAME ASC";
    
                $query = sql($sql, $eo);
    
                $tables = array();
    
                while($res = db_fetch_assoc($query)){
                    $res = array_map("mb_strtolower", $res);
    
                    $tables[$res["tbl"]] = explode("|", $res["cols"]);
                }
    
                $this -> base = $tables;
    
                // Sets meta data to the response
                $this -> meta = array(
                    "ip" => $_SERVER['REMOTE_ADDR'],
                    "timestamp" => date("Y-m-d H:i:s"),
                );
            }
        }

        function getAppGiniLib(){

            $appGiniPath = $_SERVER['DOCUMENT_ROOT'];
    
            // Try to find lib.php in the root
            $possiblePath = glob( "{$appGiniPath}/lib.php" );

            if( !empty($possiblePath) ) {
                
                return "{$appGiniPath}/lib.php";
            } else {

                // If nothing is found, search down the folders till the API folder
                $root = explode( "/", $appGiniPath );
                $file = explode( "/", str_replace( "\\", "/", __FILE__ ) );
                
                // Defines a list of parent folder to search
                $relPath = array_values( array_diff( $file, $root ) );
                $lastPathEl = count($relPath) - 1;
                unset($relPath[$lastPathEl]);

                $found = false;
                $i = 0;

                while( !$found ) {

                    // If all the parent folders' search fails, forcibly breaks the loop
                    if( !array_key_exists($i, $relPath ) ) break;

                    $appGiniPath .= "/{$relPath[$i]}";

                    $possiblePath = glob( "{$appGiniPath}/lib.php" );
                    
                    // If the lib.php file is found, saves it's path
                    if( !empty($possiblePath) ) {
                        $appGiniPath = $possiblePath[0];
                        $found = true;
                    } else {
                        $i++;
                    }
                }

                // If the loop was forcibly broken, returns an error
                if( !$found ){
                    $this -> setError("appgini-failed");
                    return false;
                }

                return $appGiniPath;
            }
        }

        // Prints the JSON reponse
        function __destruct(){
            echo json_encode($this, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK);
        }

        // Validates the user's authentication
        function validateCredential($token){
            $user = strtolower(trim($token["user"]));
            $pass = $token["pass"];

            // Checks if the given username is valid
            $userExists = array_key_exists($user, $this -> tokens);
            $correctCredentials = false;

            // Checks if the given password matches the user's password
            if($userExists) $correctCredentials = (password_verify($pass, $this -> tokens[$user]) || $this -> tokens[$user] == md5($pass));

            // Unsets the Admins' info
            unset($this -> tokens);

            if($userExists && $correctCredentials) return true;

            $this -> setError("login-failed");
            return false;
        }

        // Recieves the request data
        function getRequest($request){
            // Checks if the parameter is set but empty
            if(isset($request["tb"]) && empty($request["tb"])){
                $this -> setError("table-null");
                return false;
            }

            // If the parameter is not set, defines "all", so it can search for the database mirror
            if(!isset($request["tb"])){
                $request["tb"] = "all";
            }

            $request["tb"] = trim(mb_strtolower($request["tb"]));

            // This parameters can be an array of values
            $params = array("search", "orderBy", "orderDir");

            foreach($params as $param){
                if(isset($request[$param])){
                    // Turns GET data into an array
                    if($_SERVER["REQUEST_METHOD"] == "GET"){
                        $request[$param] =  (substr_count($request[$param], " ") ?
                                                explode(" ", $request[$param]) :
                                                explode("+", $request[$param])
                                            );
                    }
                    // Turns POST data single values into an array
                    if(!is_array($request[$param])) $request[$param] = array($request[$param]);

                    $request[$param] = array_map("mb_strtolower", $request[$param]);
                }
            }

            $this -> request = $request;
            return true;
        }

        // Error reporting method
        function setError($type){

            $errors = [
                "login-failed" => "Authentication failed",
                "table-failed" => "Nonexistent table",
                "table-null" => "Table not informed",
                "reg-null" => "There's no returned data to this query",
                "reg-failed" => "Incorrect query parameters",
                "order-failed" => "Incorrect Order field",
                "orderDir-failed" => "Incorrect Order direction",
                "orderCount-failed" => "The quantity of fields to order and order directions should be the same for 2 fields or more",
                "orderExists-failed" => "The quantity of fields to order and order directions should be the same for 2 fields or more",
                "limit-failed" => "Limit value prohibited",
                "page-failed" => "Page value prohibited",
                "id-failed" => "ID value prohibited",
                "where-failed" => "Search operator prohibited",
                "field-failed" => "Nonexistent field",
                "appgini-failed" => "AppGini not found. Please, reinstall the API"
            ];

            $this -> report = array("error" => array($type => $errors[$type]));
        }

        // Checks if the informed table is valid
        function validTable(){
            return array_key_exists($this -> request["tb"], $this -> base);
        }

        // Checks if the informed table's field is valid
        function validField($field){
            $table = $this -> request["tb"];
            $field = strtolower(trim($field));

            return in_array($field, $this -> base[$table]);
        }

        function query(){
            
            // Checks if the table value is set
            if($this -> validTable()){
                $table = strtolower(trim($this -> request["tb"]));

                $pkField = getPKFieldName($table);

                $sqlFields = (get_sql_fields($table) ? get_sql_fields($table) : "*");
                $sqlFrom = (get_sql_from($table, true) ? get_sql_from($table, true) : $table);
                $sqlFrom = str_replace(" WHERE 1=1", "", $sqlFrom);

                $sql = "SELECT {$sqlFields} FROM {$sqlFrom}";

                // Checks if a single row is requested
                if(isset($this -> request["id"])){

                    $id = intval($this -> request["id"]);

                    if(!$id){
                        $this -> setError("id-failed");
                        return;
                    }

                    $sqlWhere = " WHERE {$table}.{$pkField} = '{$id}'";

                    $sql .= $sqlWhere;
                
                // If false, creates a query
                } else {

                    $limit = 30;
                    $offset = 1;

                    $sqlWhere = "";
                    $sqlOrderBy = (substr_count($table, "membership") ? "" :" ORDER BY {$table}.id");
                    $sqlOrderDir = (substr_count($table, "membership") ? "" : " DESC");
                    $sqlLimit = " LIMIT {$limit} OFFSET ";

                    // Validates and adds the searched info to the query
                    if(isset($this -> request["search"])){

                        $sqlWhere = " WHERE";

                        foreach($this -> request["search"] as $count => $search){
                            $search = urldecode($search);

                            preg_match("/(\w+)(\W+)(.*)/", $search, $matches);

                            list($full, $field, $op, $value) = $matches;

                            $field = makeSafe(trim(strtolower($field)));

                            if(!$this -> validField($field)){
                                $this -> setError("field-failed");
                                return;
                            }

                            $value = makeSafe(trim($value));

                            if($count > 0) $sqlWhere .= " AND";

                            switch(trim($op)){
                                case ":": $sqlWhere .= " {$table}.{$field} = '{$value}'"; break;
                                case "!:": $sqlWhere .= " {$table}.{$field} <> '{$value}'"; break;
                                case "*": $sqlWhere .= " {$table}.{$field} IS NULL"; break;
                                case "!*": $sqlWhere .= " {$table}.{$field} IS NOT NULL"; break;
                                case "::": $sqlWhere .= " {$table}.{$field} LIKE '%{$value}%'"; break;
                                case "!::": $sqlWhere .= " {$table}.{$field} NOT LIKE '%{$value}%'"; break;
                                case ">": $sqlWhere .= " {$table}.{$field} > '{$value}'"; break;
                                case ">:": $sqlWhere .= " {$table}.{$field} >= '{$value}'"; break;
                                case "<": $sqlWhere .= " {$table}.{$field} < '{$value}'"; break;
                                case "<:": $sqlWhere .= " {$table}.{$field} <= '{$value}'"; break;
                                case "><":
                                    list($value1, $value2) = explode("|", $value);
                                    $sqlWhere .= " {$table}.{$field} > '{$value1}' AND {$table}.{$field} < '{$value2}'"; break;
                                case ":><":
                                    list($value1, $value2) = explode("|", $value);
                                    $sqlWhere .= " {$table}.{$field} >= '{$value1}' AND {$table}.{$field} < '{$value2}'"; break;
                                case "><:":
                                    list($value1, $value2) = explode("|", $value);
                                    $sqlWhere .= " {$table}.{$field} > '{$value1}' AND {$table}.{$field} <= '{$value2}'"; break;
                                case ":><:":
                                    list($value1, $value2) = explode("|", $value);
                                    $sqlWhere .= " {$table}.{$field} >= '{$value1}' AND {$table}.{$field} <= '{$value2}'"; break;
                                case "@":
                                    $values = implode(", ", array_trim(explode("|", $value)));
                                    $sqlWhere .= " {$table}.{$field} IN ({$values})"; break;
                                case "!@":
                                    $values = implode(", ", array_trim(explode("|", $value)));
                                    $sqlWhere .= " {$table}.{$field} NOT IN ({$values})"; break;
                                default:
                                    $this -> setError("where-failed");
                                    return;
                            }
                        }
                    }

                    // Validates and adds an ordenation to the query
                    if(isset($this -> request["orderBy"])) {

                        foreach($this -> request["orderBy"] as $i => $orderBy){
                            if(!$this -> validField($orderBy)){
                                $this -> setError("order-failed");
                                return;
                            }

                            $this -> request["orderBy"][$i] = "{$table}.{$orderBy}";
                        }
                    }

                    // Validates and adds an ordenation direction to the query
                    if(isset($this -> request["orderDir"])){

                        $countOrderDir = count($this -> request["orderDir"]);
                        $orderByExists = isset($this -> request["orderBy"]);

                        // Checks if there is more than 1 ordenation direction but no ordenation fields
                        if(!$orderByExists && $countOrderDir > 1){
                            $this -> setError("orderExists-failed");
                            return;
                        }

                        // Checks if there the ordernation direction are asc/desc only
                        foreach($this -> request["orderDir"] as $orderDir){
                            $orderDir = strtolower(trim($orderDir));

                            if(!in_array($orderDir, array("asc", "desc"))){
                                $this -> setError("orderDir-failed");
                                return;
                            }
                        }

                        if($countOrderDir == 1){
                            $sqlOrderDir = $this -> request["orderDir"][0];

                            if($orderByExists) $sqlOrderBy = " ORDER BY ". implode(", ", $this -> request["orderBy"]);

                        } else {
                            $countOrderBy = count($this -> request["orderBy"]);

                            // Checks if the quantity of ordenation fields and ordenation directions are the same
                            if($countOrderBy != $countOrderDir){
                                $this -> setError("orderCount-failed");
                                return;
                            }

                            foreach($this -> request["orderBy"] as $i => $orderBy){
                                $this -> request["orderBy"][$i] = "{$orderBy} {$this -> request["orderDir"][$i]}";
                            }

                            $sqlOrderBy = " ORDER BY ". implode(", ", $this -> request["orderBy"]);
                            $sqlOrderDir = "";
                        }
                    }

                    $sqlOrderBy .= " ". $sqlOrderDir;

                    // Checks if an especific page was requested
                    if(isset($this -> request["page"])){
                        $page = intval($this -> request["page"]);

                        if(!$page){
                            $this -> setError("page-failed");
                            return;
                        }

                        $offset = $page;
                    }

                    // Checks if limit of rows was altered
                    if(isset($this -> request["limit"])) {

                        $limit = intval($this -> request["limit"]);

                        // If the limit value is numeric, changes the limit value
                        if($limit){
                            $offset = $limit * ($offset - 1);

                            $sqlLimit = " LIMIT {$limit} OFFSET {$offset}";

                        // If it's not numeric, but it's value is "all", remove limitation from the query
                        } else if(strtolower(trim($this -> request["limit"])) == "all"){
                            $sqlLimit = "";
                        // Else, returns an error
                        } else{
                            $this -> setError("limit-failed");
                            return;
                        }
                    } else {
                        $offset = $limit * ($offset - 1);

                        $sqlLimit .= "{$offset}";
                    }

                    $sql .= $sqlWhere . $sqlOrderBy . $sqlLimit;
                }

                // Try to do the query
                try {
                    $query = sql($sql, $eo);
                    $regs = array();
                    $hasRegs = false;

                    do {
                        if(!empty($row)){
                            $hasRegs = true;

                            $regs[] = $row;
                        }
                    } while($row = db_fetch_assoc($query));

                    $this -> report = $regs;

                    // If there's no return data, informs an error
                    if(!$hasRegs) $this -> setError("reg-null");
                // Catch possible query errors
                } catch(Throwable $t) { // PHP 7
                    $this -> setError("reg-failed");
                } catch(Exception $e){  // PHP 5.6
                    $this -> setError("reg-failed");
                }

            // Checks if the database mirror were requested
            } else if(strtolower(trim($this -> request["tb"])) == "all") {

                $base = $this -> base;

                $resp = [
                    "tabelas" => $this -> base
                ];

                $this -> report = $resp;

            // The table does not exists in the database
            } else {
                $this -> setError("table-failed");
            }
        }
    }

    /* ---------- API Usage ---------- */

    // User informed values for username and password
    $token = [
        "user" => "",
        "pass" => ""
    ];

    if(array_key_exists("PHP_AUTH_USER", $_SERVER)) $token["user"] = $_SERVER["PHP_AUTH_USER"];
    if(array_key_exists("PHP_AUTH_PW", $_SERVER)) $token["pass"] = $_SERVER["PHP_AUTH_PW"];

    // Initiates the API
    $api = new api();

    // If the token was validated, continue.
    // Else, returns and error
    if($api -> validateCredential($token)){
        $data = ($_SERVER["REQUEST_METHOD"] == "GET" ?                      // Was it a GET request?
                    $_GET :                                                 // If true, use the $_GET array
                    json_decode(file_get_contents('php://input'), true)     // Else, use the JSON from POST
                );

        // Checks if the request has errors
        if($api -> getRequest($data)){
            // If true, do the query
            // Else, informs an error
            $api -> query();
        }
    }

?>

But functions like getAppGiniLib(), sql() seems to be not working
Can our experts comment on it so many of us can use this API to expand the possibility of integrating AppGini with other systems?
Ofcause the security, authentication and token handling is another important area we should discuss in this topic

Re: AppGini API

Posted: 2020-12-15 00:25
by Alisson
For the API to work again just include config.php

V3
Find api-config.php and add this at the end:

Code: Select all

include "{$dir}/../../config.php";
Just after:

Code: Select all

include "{$dir}/../../lib.php";
V2
Find index.php and add this:

Code: Select all

include dirname(__FILE__)."/../../config.php";
Just after:

Code: Select all

include dirname(__FILE__)."/../../lib.php";

Re: AppGini API

Posted: 2020-12-15 15:29
by sathukorala
Alisson wrote:
2020-12-15 00:25
For the API to work again just include config.php

V3
Find api-config.php and add this at the end:

Code: Select all

include "{$dir}/../../config.php";
Just after:

Code: Select all

include "{$dir}/../../lib.php";
V2
Find index.php and add this:

Code: Select all

include dirname(__FILE__)."/../../config.php";
Just after:

Code: Select all

include dirname(__FILE__)."/../../lib.php";
Thanks Allison, I will check it

Re: AppGini API

Posted: 2020-12-15 19:47
by pbottcher
There is an update version (Sep. 2020) of the API available. This version seems to seach for the AppGini settings. Not sure if it works now without chages, but you could give it a try.

Re: AppGini API

Posted: 2020-12-16 02:26
by sathukorala
Alisson wrote:
2020-12-15 00:25
For the API to work again just include config.php

V3
Find api-config.php and add this at the end:

Code: Select all

include "{$dir}/../../config.php";
Just after:

Code: Select all

include "{$dir}/../../lib.php";
V2
Find index.php and add this:

Code: Select all

include dirname(__FILE__)."/../../config.php";
Just after:

Code: Select all

include dirname(__FILE__)."/../../lib.php";
Sorry Alisson, it's not working even after adding config.php
The output in V2 is

Code: Select all

<br />
<font size='1'><table class='xdebug-error xe-warning' dir='ltr' border='1' cellspacing='0' cellpadding='1'>
<tr><th align='left' bgcolor='#f57900' colspan="5"><span style='background-color: #cc0000; color: #fce94f; font-size: x-large;'>( ! )</span> Warning: array_merge(): Argument #1 is not an array in C:\wamp64\www\apitest\lib.php on line <i>39</i></th></tr>
<tr><th align='left' bgcolor='#e9b96e' colspan='5'>Call Stack</th></tr>
<tr><th align='center' bgcolor='#eeeeec'>#</th><th align='left' bgcolor='#eeeeec'>Time</th><th align='left' bgcolor='#eeeeec'>Memory</th><th align='left' bgcolor='#eeeeec'>Function</th><th align='left' bgcolor='#eeeeec'>Location</th></tr>
<tr><td bgcolor='#eeeeec' align='center'>1</td><td bgcolor='#eeeeec' align='center'>0.0014</td><td bgcolor='#eeeeec' align='right'>356576</td><td bgcolor='#eeeeec'>{main}(  )</td><td title='C:\wamp64\www\apitest\api\v2\index.php' bgcolor='#eeeeec'>...\index.php<b>:</b>0</td></tr>
<tr><td bgcolor='#eeeeec' align='center'>2</td><td bgcolor='#eeeeec' align='center'>0.2066</td><td bgcolor='#eeeeec' align='right'>2119480</td><td bgcolor='#eeeeec'>include( <font color='#00bb00'>'C:\wamp64\www\apitest\lib.php'</font> )</td><td title='C:\wamp64\www\apitest\api\v2\index.php' bgcolor='#eeeeec'>...\index.php<b>:</b>510</td></tr>
<tr><td bgcolor='#eeeeec' align='center'>3</td><td bgcolor='#eeeeec' align='center'>0.2072</td><td bgcolor='#eeeeec' align='right'>2119960</td><td bgcolor='#eeeeec'><a href='http://www.php.net/function.array-merge' target='_new'>array_merge</a>
(  )</td><td title='C:\wamp64\www\apitest\lib.php' bgcolor='#eeeeec'>...\lib.php<b>:</b>39</td></tr>
</table></font>
{"report":{"tabelas":[]},"meta":{"ip":"::1","timestamp":"2020-12-15 21:22:30"}}
V3 doesn't give any output
This is September updated API pböttcher

Re: AppGini API

Posted: 2020-12-19 02:30
by sathukorala
Anyone who has successfully implemented this API / tested successfully, please share your experience...

Re: AppGini API

Posted: 2020-12-20 09:25
by sathukorala
This is the error in the API code
( ! ) Warning: array_merge(): Argument #1 is not an array in C:\...\...\...\lib.php on line 39

Re: AppGini API

Posted: 2020-12-20 09:35
by jsetzer
First of all, I don't use this API script for various reasons, so I cannot say much about the programming of that script itself, nor do I intend to find and fix bugs.

I would just like to mention that the problem you have reported may have to do with language files and I remember that translation-logic in AppGini has changed recently (since 5.9x?). The script may need to be updated to be compatible with newer versions of appgini.

Re: AppGini API

Posted: 2020-12-20 12:02
by sathukorala
jsetzer wrote:
2020-12-20 09:35
First of all, I don't use this API script for various reasons, so I cannot say much about the programming of that script itself, nor do I intend to find and fix bugs.

I would just like to mention that the problem you have reported may have to do with language files and I remember that translation-logic in AppGini has changed recently (since 5.9x?). The script may need to be updated to be compatible with newer versions of appgini.
Thanks Jan for the reply, I know the security risk of using this script.
Any alternative you would suggest?

Re: AppGini API

Posted: 2020-12-20 13:19
by sathukorala
You are correct Jan. The API works for AppGini 5.8x but not on 5.9x
Maybe Ahmed Gneady can help us

Re: AppGini API

Posted: 2020-12-20 13:48
by jsetzer
I really appreciate those contributions and I hope those AppGineers will continue development. I would also be happy if someone could contribute a good and secure solution.
Any alternative you would suggest?
I'm sorry, I will not make recommendations here if I am not convinced of the solution.

I myself do not use any of those database-interface implementations, because I have not yet seen a complete and secure API solution that is fully integrated into the AppGini security system, uses whitelists or routing (instead of publishing all tables) and is compatible with different versions of AppGini.

Also, an API should be more than just a database-interface: An API should promise security and data integrity. A database-interface allows you to insert, change or delete whatever you want - even if this breaks referential integrity. That is too risky for my customer projects.

I prefer programming specific PHP classes (serverside) and specific JQuery code using AJAX (clientside) which exactly do what I need. On serverside I can react to the currently logged in user and return only that specific quantity and quality of data which is necessary for the specific usecase.

Re: AppGini API

Posted: 2020-12-20 14:21
by jsetzer
Maybe Ahmed Gneady can help us
Well, from my personal point of view, Ahmed should focus on a secure base application, generated by AppGini. He has enough to do with this, because requirements change and data security is becoming increasingly important. Having a secure base application is very, very important for (almost?) all of us, I guess. And I guess that only a small fraction of AppGini customers will ever have to deal with AJAX and API's. Different users have different priorities. What is important to me is (sadly) not always the most important thing for a product like AppGini and the majority of its users.

This API-stuff is more for advanced programmers who understand the technology, can read and understand PHP and Javascript and can help and contribute with their own knowledge. You should be able to anticipate the risks and pitfalls - and this requires some advanced knowledge.

From my point of view Ahmed is not responsible for bugfixing or extending other developers' contributions to keep their code compatible with new versions of his product. Even if I would like Ahmed to solve my problems: That would be asking much too much. I am grateful for every contribution, even if it is "only" a hint or starting point, from which I then have to work further myself, research, make mistakes and learn until I have found the solution to my own problem.

Please don't get me wrong, this is only my personal opinion.

At this point, a very heartfelt "thank you" to all those who help us day by day solve our problems with their contributions. I appreciate very much how you have helped me - especially in the beginning - over the one or other hurdle.

Re: AppGini API

Posted: 2020-12-20 14:32
by sathukorala
jsetzer wrote:
2020-12-20 14:21
Maybe Ahmed Gneady can help us
Well, from my personal point of view, Ahmed should focus on a secure base application, generated by AppGini. He has enough to do with this, because requirements change and data security is becoming increasingly important. Having a secure base application is very, very important for (almost?) all of us, I guess. And I guess that only a small fraction of AppGini customers will ever have to deal with AJAX and API's. Different users have different priorities. What is important to me is (sadly) not always the most important thing for a product like AppGini and the majority of its users.

This API-stuff is more for advanced programmers who understand the technology, can read and understand PHP and Javascript and can help and contribute with their own knowledge. You should be able to anticipate the risks and pitfalls - and this requires some advanced knowledge.

From my point of view Ahmed is not responsible for bugfixing or extending other developers' contributions to keep their code compatible with new versions of his product. Even if I would like Ahmed to solve my problems: That would be asking much too much. I am grateful for every contribution, even if it is "only" a hint or starting point, from which I then have to work further myself, research, make mistakes and learn until I have found the solution to my own problem.

Please don't get me wrong, this is only my personal opinion.

At this point, a very heartfelt "thank you" to all those who help us day by day solve our problems with their contributions. I appreciate very much how you have helped me - especially in the beginning - over the one or other hurdle.
Thank you so much Jan for the lengthy reply and the detailed answer.
My idea was just to open this perspective and possibility to make Appgini popular among advanced developers.
You are correct that Appgini should evolve but should not compromise its security and safety.
Let's all contribute more towards that goal.

Re: AppGini API

Posted: 2020-12-20 14:37
by jsetzer
A first step in this direction could be writing a specification for an API. With clearly defined requirements, advanced programmers could work on a solution piece by piece.

Re: AppGini API

Posted: 2020-12-21 02:40
by sathukorala
Totally agree with Jan.
BDW I found the problem with this API (Read Jan's description before use) if anyone wants to use it.

You have to include config.php in the lib.php in root folder. That's all.

Add following near line 40 in the lib.php

Code: Select all

include_once("$currDir/config.php");
Then the API v2 & v3 works fine

***Again I would say this API is a compromise of your data in AppGini, so use it carefully and bearing the risk of data exposure.
***This API is not an invention of me and the owner is mentioned in the first post.