{"id":23552957,"url":"https://github.com/rhildred/prog8020project2start","last_synced_at":"2026-01-25T01:32:05.160Z","repository":{"id":146397946,"uuid":"47890399","full_name":"rhildred/PROG8020Project2Start","owner":"rhildred","description":"This is the start for simple CRUD on a products page based site.","archived":false,"fork":false,"pushed_at":"2015-12-14T03:32:59.000Z","size":24699,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-15T17:15:39.816Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rhildred.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-12-12T18:52:22.000Z","updated_at":"2015-12-12T18:59:53.000Z","dependencies_parsed_at":"2023-04-15T21:46:48.119Z","dependency_job_id":null,"html_url":"https://github.com/rhildred/PROG8020Project2Start","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rhildred/PROG8020Project2Start","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhildred%2FPROG8020Project2Start","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhildred%2FPROG8020Project2Start/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhildred%2FPROG8020Project2Start/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhildred%2FPROG8020Project2Start/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rhildred","download_url":"https://codeload.github.com/rhildred/PROG8020Project2Start/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhildred%2FPROG8020Project2Start/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28740904,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T01:25:41.653Z","status":"ssl_error","status_checked_at":"2026-01-25T01:25:34.364Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-26T11:13:38.296Z","updated_at":"2026-01-25T01:32:05.139Z","avatar_url":"https://github.com/rhildred.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [PROG8020 Project 2 Start](https://github.com/rhildred/PROG8020Project2Start)\nThis is part 2 of a [product page](https://github.com/rhildred/PROG8020ProjectStart). This one adds basic Create Read Update and Delete (CRUD) functionality. My intention is to complete the functionality as a way of \"taking this up\" after the term is up. The thing about CRUD is that it is intertwined with security. You don't want just anyone to be able to update the products that you offer. To deal with the security we add the rhildred/oauth2 composer module.\n\n```\n{\n    \"require\": {\n        \"slim/slim\": \"^2.6\",\n        \"rhildred/slimphpviews\": \"dev-master\",\n        \"rhildred/editable\": \"dev-master\",\n        \"rhildred/oauth2\": \"dev-master\"\n    }\n}\n\n```\n\nWe also add a page views/crud.phtml. This page makes use of angular.js and the following UI to provide a user interface for CRUD:\n\n```\n\n\u003cdiv class=\"bookmark container\" ng-app=\"myApp\" ng-controller=\"myCtrl\"\u003e\n    \u003ch1\u003eProduct Maintenance\u003c/h1\u003e\n    \u003cdiv class=\"row\"\u003e\n        \u003ch3 class=\"col-md-2\"\u003eName\u003c/h3\u003e\u003ch3 class=\"col-md-6\"\u003eDescription\u003c/h3\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"row\" ng-repeat=\"product in products track by $index\"\u003e\n        \u003cdiv class=\"col-md-2\"\u003e{{product.name}}\u003c/div\u003e\u003cdiv class=\"col-md-6\"\u003e{{product.description}}\u003c/div\u003e\n        \u003cdiv class=\"col-md-4\"\u003e\n            \u003cbutton type=\"button\" class=\"btn btn-primary btn-large\" data-toggle=\"modal\" data-target=\"#createUpdate\" ng-click=\"editProduct($index)\"\u003e\u003ci class=\"fa fa-pencil\"\u003e\u003c/i\u003e\u0026nbsp;Edit\u003c/button\u003e\n            \u003cbutton type=\"button\" class=\"btn btn-danger btn-large\" data-toggle=\"modal\" data-target=\"#delete\" ng-click=\"checkDelete($index)\"\u003e\u003ci class=\"fa fa-remove\"\u003e\u003c/i\u003e\u0026nbsp;Delete\u003c/button\u003e\n        \u003c/div\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"row\"\u003e\n        \u003cbutton type=\"button\" class=\"btn btn-primary btn-large\" data-toggle=\"modal\" data-target=\"#createUpdate\" ng-click=\"addProduct()\"\u003e\u003ci class=\"fa fa-plus\"\u003e\u003c/i\u003e\u0026nbsp;Add\u003c/button\u003e\n\n    \u003c/div\u003e\n    \u003c!-- Create/Update --\u003e\n    \u003cdiv class=\"modal fade\" id=\"createUpdate\" role=\"dialog\"\u003e\n        \u003cdiv class=\"modal-dialog\"\u003e\n\n            \u003c!-- Modal content--\u003e\n            \u003cdiv class=\"modal-content\"\u003e\n                \u003cdiv class=\"modal-header\"\u003e\n                    \u003cbutton type=\"button\" class=\"close\" data-dismiss=\"modal\"\u003e\u0026times;\u003c/button\u003e\n                    \u003ch4 class=\"modal-title\"\u003e{{title}}\u003c/h4\u003e\n                \u003c/div\u003e\n                \u003cdiv class=\"modal-body\"\u003e\n                    \u003cform class=\"form-horizontal\" role=\"form\"\u003e\n                          \u003cdiv class=\"form-group\"\u003e\n                            \u003clabel class=\"control-label col-sm-3\" for=\"productname\"\u003eName:\u003c/label\u003e\n                            \u003cdiv class=\"col-sm-9\"\u003e\n                                \u003cinput ng-model=\"currentProduct.name\" placeholder=\"product name\" id=\"productname\" class=\"form-control\"/\u003e\n                            \u003c/div\u003e\n                          \u003c/div\u003e\n                          \u003cdiv class=\"form-group\"\u003e\n                            \u003clabel class=\"control-label col-sm-3\" for=\"productdescription\"\u003eDescription:\u003c/label\u003e\n                            \u003cdiv class=\"col-sm-9\"\u003e \n                                \u003ctextarea ng-model=\"currentProduct.description\" placeholder=\"product description\" id=\"productdescription\" class=\"form-control\"\u003e\u003c/textarea\u003e\n                            \u003c/div\u003e\n                          \u003c/div\u003e\n                    \u003c/form\u003e\n                    \n                \u003c/div\u003e\n                \u003cdiv class=\"modal-footer\"\u003e\n                    \u003cbutton type=\"button\" class=\"btn btn-primary\" data-dismiss=\"modal\" ng-click=\"saveProduct()\"\u003e\u003ci class=\"fa fa-database\"\u003e\u003c/i\u003e\u0026nbsp;Save\u003c/button\u003e\n                    \u003cbutton type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\"\u003eCancel\u003c/button\u003e\n                \u003c/div\u003e\n            \u003c/div\u003e\n\n        \u003c/div\u003e\n\n    \u003c/div\u003e\n\n\n```\n\nThe `createUpdate` dialog is a standard bootstrap dialog that gets triggered by the buttons using the bootstrap javascript. As the dialogs are triggered though, the angular `addProduct` and `editProduct` methods are called to set up the contents of the dialog.\n\n```\n        $scope.editProduct = function(nProduct){\n            // get ready to show the dialog\n            angular.extend($scope.currentProduct, $scope.products[nProduct]);\n            $scope.nCurrent = nProduct;\n            $scope.title=\"Edit Product\";\n        };\n        $scope.addProduct = function(){\n            // get ready to show the dialog\n            $scope.currentProduct = {};\n            $scope.nCurrent = -1;\n            $scope.title=\"Add Product\";\n        };\n\n\n```\n\nThe crud methods themselves are invoked buttons on the dialogs themselves.\n\n```\n\n        $scope.saveProduct = function(){\n            if($scope.nCurrent == -1){\n                // then add a new product to the list\n                $http.post(\"/crud/products\", $scope.currentProduct).success(function(oProduct){\n                    $scope.products.push(oProduct);\n                }).error(failure);\n            }else{\n                // copy the product back in to the list\n                $http.post(\"/crud/products/\" + $scope.currentProduct.productID, $scope.currentProduct).success(function(oProduct){\n                    $scope.products[$scope.nCurrent] = oProduct;\n                }).error(failure);\n            }\n        };\n        $scope.deleteProduct = function(){\n            // remove the product from the list\n            $http.delete(\"/crud/products/\" + $scope.currentProduct.productID).success(function(){\n                $scope.products.splice($scope.nCurrent, 1);\n            }).error(failure);\n        };\n\n\n```\n\nThese methods call the index.php crud/products endpoint which are protected by `new \\Auth()` from being accessed unless the user is logged in:\n\n```\n\n$oApp-\u003eget(\"/crud/products\", new \\Auth(), function() use($oDb){\n    $oStmt = $oDb-\u003eprepare(\"SELECT * FROM products\");\n    $oStmt-\u003eexecute();\n    $aProducts = $oStmt-\u003efetchAll(PDO::FETCH_OBJ);\n    echo json_encode($aProducts);\n});\n$oApp-\u003epost(\"/crud/products\", new \\Auth(), function() use($oDb, $oApp){\n    $oData = json_decode($oApp-\u003erequest-\u003egetBody());\n    $oStmt = $oDb-\u003eprepare(\"INSERT INTO products(name, description) VALUES(:name, :description)\");\n    $oStmt-\u003ebindParam(\"name\", $oData-\u003ename);\n    $oStmt-\u003ebindParam(\"description\", $oData-\u003edescription);\n    $oStmt-\u003eexecute();\n    $oData-\u003eproductID = $oDb-\u003elastInsertId();\n    echo json_encode($oData);\n});\n$oApp-\u003epost(\"/crud/products/:productID\", new \\Auth(), function($nProductID) use($oDb, $oApp){\n    $oData = json_decode($oApp-\u003erequest-\u003egetBody());\n    $oStmt = $oDb-\u003eprepare(\"UPDATE products SET name = :name, description = :description where productID = :id\");\n    $oStmt-\u003ebindParam(\"name\", $oData-\u003ename);\n    $oStmt-\u003ebindParam(\"description\", $oData-\u003edescription);\n    $oStmt-\u003ebindParam(\"id\", $nProductID);\n    $oStmt-\u003eexecute();\n    echo json_encode($oData);\n});\n$oApp-\u003edelete(\"/crud/products/:productID\", new \\Auth(), function($nProductID) use($oDb, $oApp){\n    $oStmt = $oDb-\u003eprepare(\"DELETE FROM products where productID = :id\");\n    $oStmt-\u003ebindParam(\"id\", $nProductID);\n    $oStmt-\u003eexecute();\n    echo '{\"result\":\"success\"}';\n});\n\n\n\n```\n\nUsers become logged in by accessing the login url:\n\n```\n\n// endpoints for auth\n$oApp-\u003eget(\"/login\", function() use( $oApp){\n    // see if this is the original redirect or if it's the callback\n    $sCode = $oApp-\u003erequest-\u003eparams('code');\n    // get the uri to redirect to\n    $sUrl = \"http\";\n    if(!empty($_SERVER['HTTPS']) \u0026\u0026 $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443)\n    {\n        $sUrl .= \"s\";\n    }\n    $sUrl .= \"://\" . $_SERVER[\"HTTP_HOST\"] . $_SERVER[\"REQUEST_URI\"];\n    $oAuth = new \\Oauth2($sUrl);\n    if($sCode == null){\n        $oApp-\u003eresponse-\u003eredirect($oAuth-\u003eredirectUrl());\n    }else{\n        $oAuth-\u003ehandleCode($sCode);\n        $oApp-\u003eresponse-\u003eredirect(\"/\");\n    }\n});\n$oApp-\u003eget(\"/currentUser\", new \\Auth(), function() use($oApp){\n    echo json_encode($_SESSION['CurrentUser']);\n});\n$oApp-\u003eget(\"/logout\", function(){\n    session_start();\n    unset($_SESSION[\"CurrentUser\"]);\n});\n\n```\n\n which in turn accesses a file holding google oauth2 credentials /creds/google.json\n\n\n```\n\n{\"ClientID\":\"\u003cyour client id\u003e\", \"ClientSecret\":\"\u003cyour client secret\u003e\",\n\"Users\":[\"\u003cemail of person you want to be able to update\u003e\"]}\nStatus API Training Shop Blog About Pricing\n\n\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frhildred%2Fprog8020project2start","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frhildred%2Fprog8020project2start","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frhildred%2Fprog8020project2start/lists"}